Invertir variable booleana

26

Quiero probar un script simple

flag=false
while !$flag
do
   read x
   if [ "$x" -eq "true" ]
   then
     flag=true
   fi
   echo "${x} : ${flag}"
done

Pero cuando lo ejecuto, si escribo true, lo veré x="true"y flag="true", pero el ciclo no termina. ¿Qué tiene de malo el guión? ¿Cómo puedo invertir correctamente una variable booleana?

sueco
fuente
¡Gracias! lo tengo ahora
sueco

Respuestas:

21

Hay dos errores en su script. El primero es que necesita un espacio entre !y $flag, de lo contrario, el shell busca un comando llamado !$flag. El segundo error es que -eqes para comparaciones de enteros, pero lo está utilizando en una cadena. Dependiendo de su shell, verá un mensaje de error y el ciclo continuará para siempre porque la condición [ "$x" -eq "true" ]no puede ser verdadera, o cada valor no entero se tratará como 0 y el ciclo se cerrará si ingresa cualquier cadena (incluida false) que no sea un número diferente de 0.

Si bien ! $flages correcto, es una mala idea tratar una cadena como un comando. Funcionaría, pero sería muy sensible a los cambios en su secuencia de comandos, ya que necesitaría asegurarse de que $flagnunca pueda ser otra cosa que trueo false. Sería mejor usar una comparación de cadenas aquí, como en la prueba a continuación.

flag=false
while [ "$flag" != "true" ]
do
   read x
   if [ "$x" = "true" ]
   then
     flag=true
   fi
   echo "${x} : ${flag}"
done

Probablemente haya una mejor manera de expresar la lógica que busca. Por ejemplo, podría hacer un bucle infinito y romperlo cuando detecte la condición de terminación.

while true; do
  read -r x
  if [ "$x" = "true" ]; then break; fi
  echo "$x: false"
done
Gilles 'SO- deja de ser malvado'
fuente
Con el valor [incorporado de ksh, [ x -eq y ]devuelve verdadero si la xexpresión aritmética se resuelve en el mismo número que la yexpresión aritmética, por lo que, por ejemplo, x=2 true=1+1 ksh -c '[ x -eq true ] && echo yes'generaría sí.
Stéphane Chazelas
10

Si bien no hay variables booleanas en Bash, es muy fácil emularlas mediante la evaluación aritmética.

flag= # False
flag=0 # False
flag=1 # True (actually any integer number != 0 will do, but see remark below about toggling)
flag="some string" # Maybe False (make sure that the string isn't interpreted as a number)

if ((flag)) # Test for True
then
  : # do something
fi

if ! ((flag)) # Test for False
then
  : # do something
fi

flag=$((1-flag)) # Toggle flag (only works when flag is either empty or unset, 0, 1, or a string which doesn't represent a number)

Esto también funciona en ksh. No me sorprendería si funciona en todos los shells compatibles con POSIX, pero no he verificado el estándar.

ack
fuente
1
¿ ! ! ((val))Funciona en Bash, como en C o C ++? Por ejemplo, si vales 0, sigue siendo 0 después ! !. Si vales 1, queda 1 después ! !. Si vales 8, se baja a 1 después ! !. ¿O necesito hacer otra pregunta?
1
-1 | En POSIX sh, independiente ((..)) no está definido; por favor elimine la referencia a él
LinuxSecurityFreak
Vlastimil la pregunta específicamente es sobre bash, no posix shell. ¿Por qué rechazar el voto cuando la pregunta no es sobre ese tema?
Nick Bull
6

Si puede estar absolutamente seguro de que la variable contendrá 0 o 1, puede usar el operador XOR igual a bit para cambiar entre los dos valores:

$ foo=0
$ echo $foo
0
$ ((foo ^= 1))
$ echo $foo
1
$ ((foo ^= 1))
$echo $foo
0
usuario971217
fuente
2

Esto funciona para mi:

flag=false
while [[ "$flag" == "false" ]]
do
   read x
   if [[ "$x" == "true" ]]
   then
     flag=true
   fi
   echo "${x} : ${flag}"
done

Pero, en realidad, lo que debe hacer es reemplazar su whilecon:

while ! $flag
ДМИТРИЙ МАЛИКОВ
fuente
0

No existe el concepto de una variable booleana en el shell.
Las variables de shell sólo podían ser text(una cadena), y, en algunos casos, que el texto puede ser interpretado como un entero ( 1, 0xa, 010, etc.).

Por lo tanto, a flag=trueno implica veracidad o falsedad alguna para el caparazón.

Cuerda

Lo que se podría hacer es una comparación de cadenas [ "$flag" == "true" ]o usar el contenido variable en algún comando y verificar sus consecuencias , como ejecutar true(porque hay un ejecutable llamado truey uno llamado false) como un comando y verificar si el código de salida de ese comando es cero (exitoso).

$flag; if [ "$?" -eq 0 ]; then ... fi

O más corto:

if "$flag"; then ... fi

Si el contenido de una variable se usa como un comando, a !podría usarse para negar el estado de salida del comando, si existe un espacio entre ambos ( ! cmd), como en:

if ! "$flag"; then ... fi

El script debería cambiar a:

flag=false
while ! "$flag" 
do
   read x
   if [ "$x" == "true" ]
   then
     flag=true
   fi
   echo "${x} : ${flag}"
done

Entero

Use valores numéricos y expansiones aritméticas .

En este caso, el código de salida de $((0))is 1y el código de salida de $((1))is 0.
En bash, ksh y zsh, la aritmética podría llevarse a cabo dentro de a ((..))(tenga en cuenta que $falta el inicio ).

flag=0; if ((flag)); then ... fi

Una versión portátil de este código es más complicada:

flag=0; if [ "$((flag))" -eq 0 ]; then ... fi      # test for a number
flag=0; if [ "$((flag))"  == 0 ]; then ... fi      # test for the string "0"

En bash / ksh / zsh puedes hacer:

flag=0    
while ((!flag)) 
do
   read x
   [ "$x" == "true" ] && flag=1
   echo "${x} : ${flag}"
done

Alternativamente

Puede "Invertir una variable booleana" (siempre que contenga un valor numérico) como:

((flag=!flag))

Eso va a cambiar el valor de flagque sea 0o 1.


Nota : compruebe si hay errores en https://www.shellcheck.net/ antes de publicar su código como una pregunta, muchas veces es suficiente para encontrar el problema.

Isaac
fuente
0

untiles la abreviatura de while !(y until, al contrario que !Bourne), por lo que puede hacer:

flag=false
until "$flag"
do
   IFS= read -r x
   if [ "$x" = true ]
   then
     flag=true
   fi
   printf '%s\n' "$x : $flag"
done

Que debería ser lo mismo que:

flag=false
while ! "$flag"
do
   IFS= read -r x
   if [ "$x" = true ]
   then
     flag=true
   fi
   printf '%s\n' "$x : $flag"
done

También puedes escribirlo como:

until
   IFS= read -r x
   printf '%s\n' "$x"
   [ "$x" = true ]
do
   continue
done

La parte entre until/ whiley dono tiene que ser un solo comando.

!es una palabra clave en la sintaxis de shell POSIX. Debe estar delimitado, un token separado de lo que sigue y ser el primer token que precedió a una tubería ( ! foo | barniega la tubería y foo | ! barno es válido, aunque algunos shells lo aceptan como una extensión).

!truese interpreta como el !truecomando que es poco probable que exista. ! trueDeberia trabajar. !(true)debería funcionar en shells compatibles con POSIX ya que !está delimitado ya que está seguido por un (token, pero en la práctica no funciona en ksh/ bash -O extglobdonde entra en conflicto con el !(pattern)operador de globo extendido ni zsh (excepto en la emulación sh) donde entra en conflicto glob(qualifiers)con bareglobqualy glob(group)sin.

Stéphane Chazelas
fuente
-1
[ ${FOO:-0} == 0 ] && FOO=1 || FOO=0

o incluso:

[ ${FOO:-false} == false ] && FOO=true || FOO=false

debería hacer el trabajo

slaxor
fuente