Inverser la variable booléenne

26

Je veux essayer un script simple

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

Mais quand je l'exécute, si je tape true, je verrai cela x="true"et flag="true", mais le cycle ne se termine pas. Quel est le problème avec le script? Comment inverser correctement une variable booléenne?

élever
la source
THX! je l'ai maintenant
raceish

Réponses:

21

Il y a deux erreurs dans votre script. La première est que vous avez besoin d'un espace entre !et $flag, sinon le shell recherche une commande appelée !$flag. La deuxième erreur -eqconcerne les comparaisons d'entiers, mais vous l'utilisez sur une chaîne. En fonction de votre shell, soit vous verrez un message d'erreur et la boucle continuera indéfiniment car la condition [ "$x" -eq "true" ]ne peut pas être vraie, soit chaque valeur non entière sera traitée comme 0 et la boucle se fermera si vous entrez une chaîne (y compris false) autre qu'un nombre différent de 0.

Bien que ce ! $flagsoit correct, c'est une mauvaise idée de traiter une chaîne comme une commande. Cela fonctionnerait, mais il serait très sensible aux changements dans votre script, car vous devez vous assurer que $flagcela ne peut jamais être que trueou false. Il serait préférable d'utiliser une comparaison de chaînes ici, comme dans le test ci-dessous.

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

Il y a probablement une meilleure façon d'exprimer la logique que vous recherchez. Par exemple, vous pouvez créer une boucle infinie et la rompre lorsque vous détectez la condition de terminaison.

while true; do
  read -r x
  if [ "$x" = "true" ]; then break; fi
  echo "$x: false"
done
Gilles 'SO- arrête d'être méchant'
la source
Avec la fonction [intégrée de ksh, [ x -eq y ]retournez true si l' xexpression arithmétique se résout au même nombre que l' yexpression arithmétique, par exemple, x=2 true=1+1 ksh -c '[ x -eq true ] && echo yes'elle afficherait oui.
Stéphane Chazelas
10

Bien qu'il n'y ait pas de variables booléennes dans Bash, il est très facile de les émuler en utilisant une évaluation arithmétique.

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)

Cela fonctionne également dans ksh. Je ne serais pas surpris si cela fonctionne dans tous les shells compatibles POSIX, mais je n'ai pas vérifié la norme.

ack
la source
1
Fonctionne-t-il ! ! ((val))dans Bash, comme en C ou C ++? Par exemple, si valest 0, il reste 0 après ! !. Si valest 1, il reste 1 après ! !. Si valc'est 8, il est ramené à 1 après ! !. Ou dois-je poser une autre question?
1
-1 | Dans POSIX sh, autonome ((..)) n'est pas défini; veuillez supprimer la référence
LinuxSecurityFreak
Vlastimil la question concerne spécifiquement bash - pas le shell posix. Pourquoi voter contre quand la question ne porte pas sur ce sujet?
Nick Bull
6

Si vous pouvez être absolument sûr que la variable contiendra 0 ou 1, vous pouvez utiliser l'opérateur XOR égal au niveau du bit pour basculer entre les deux valeurs:

$ foo=0
$ echo $foo
0
$ ((foo ^= 1))
$ echo $foo
1
$ ((foo ^= 1))
$echo $foo
0
user971217
la source
2

Cela fonctionne pour moi:

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

Mais, en fait, ce que vous devez faire est de remplacer le vôtre whilepar:

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

Il n'y a pas de concept de variable booléenne dans le shell.
Les variables du shell ne pouvaient être text(une chaîne), et que le texte dans certains cas , peut être interprété comme un entier ( 1, 0xa, 010, etc.).

Par conséquent, a flag=truen'implique aucune véracité ou fausseté du tout.

Chaîne

Ce qui pourrait être fait est une comparaison de chaînes [ "$flag" == "true" ]ou utiliser le contenu variable dans une commande et vérifier ses conséquences , comme exécuter true(car il y a à la fois un exécutable appelé trueet un appelé false) comme commande et vérifier si le code de sortie de cette commande est nul (réussi).

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

Ou plus court:

if "$flag"; then ... fi

Si le contenu d'une variable est utilisé comme une commande, un !pourrait être utilisé pour annuler l'état de sortie de la commande, s'il existe un espace entre les deux ( ! cmd), comme dans:

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

Le script doit se changer en:

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

Entier

Utilisez des valeurs numériques et des extensions arithmétiques .

Dans ce cas, le code de sortie de $((0))is 1et le code de sortie de $((1))is 0.
En bash, ksh et zsh, l'arithmétique pourrait être effectuée à l'intérieur de a ((..))(notez que le démarrage $est manquant).

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

Une version portable de ce code est plus compliquée:

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

Dans bash / ksh / zsh, vous pouvez faire:

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

Alternativement

Vous pouvez "Inverser une variable booléenne" (à condition qu'elle contienne une valeur numérique) comme:

((flag=!flag))

Cela changera la valeur flagsoit 0ou 1.


Remarque : veuillez vérifier les erreurs dans https://www.shellcheck.net/ avant de publier votre code sous forme de question, plusieurs fois cela suffit pour trouver le problème.

Isaac
la source
0

untilest l'abréviation de while !(et untilcontrairement à !Bourne), vous pouvez donc faire:

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

Ce qui devrait être le même que:

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

Vous pouvez également l'écrire comme suit:

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

La partie entre le until/ whileet done doit pas être une seule commande.

!est un mot-clé dans la syntaxe du shell POSIX. Il doit être délimité, un jeton séparé de ce qui suit et être le premier jeton qui a précédé un pipeline ( ! foo | barannule le pipeline et foo | ! barn'est pas valide bien que certains shells l'acceptent comme une extension).

!trueest interprétée comme la !truecommande qui a peu de chances d'exister. ! truedevrait marcher. !(true)devrait fonctionner dans des shells compatibles POSIX !car il est délimité car il est suivi d'un (jeton, mais en pratique, il ne fonctionne pas dans ksh/ bash -O extgloboù il entre en conflit avec l' !(pattern)opérateur glob étendu ni dans zsh (sauf dans l'émulation sh) où il entre en conflit avec glob(qualifiers)et bareglobqualet glob(group)sans pour autant.

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

ou même:

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

devrait faire le travail

slaxor
la source