Priorité des opérateurs logiques shell &&, ||

126

J'essaie de comprendre comment fonctionne la priorité des opérateurs logiques dans bash. Par exemple, je m'attendais à ce que la commande suivante ne répète rien.

true || echo aaa && echo bbb

Cependant, contrairement à mes attentes, bbbest imprimé.

Quelqu'un peut-il s'il vous plaît expliquer, comment puis-je comprendre les opérateurs &&et les ||opérateurs dans bash?

Martin Vegter
la source

Réponses:

127

Dans de nombreux langages informatiques, les opérateurs ayant la même priorité sont associatifs à gauche . C'est-à-dire qu'en l'absence de structures de regroupement, les opérations les plus à gauche sont exécutées en premier. Bash ne fait pas exception à cette règle.

Ceci est important car, dans Bash, &&et ||ont la même priorité.

Donc, ce qui se passe dans votre exemple, c'est que l'opération la plus à gauche ( ||) est exécutée en premier:

true || echo aaa

Puisque trueest évidemment vrai, l’ ||opérateur court-circuite et toute la déclaration est considérée comme vraie sans qu’il soit nécessaire d’évaluer echo aaacomme vous le voudriez. Maintenant, il reste à faire l'opération la plus à droite:

(...) && echo bbb

Depuis la première opération évaluée à true (c’est-à-dire avec un statut de sortie égal à 0), c’est comme si vous exécutiez

true && echo bbb

donc le &&sera pas court-circuit, ce qui explique pourquoi vous voyez bbbécho.

Vous auriez le même comportement avec

false && echo aaa || echo bbb

Notes basées sur les commentaires

  • Notez que la règle d’associativité gauche est suivie uniquement lorsque les deux opérateurs ont la même priorité. Ce n'est pas le cas lorsque vous utilisez ces opérateurs avec des mots-clés tels que [[...]]ou ((...))ou utilisez les opérateurs -oet -acomme arguments des commandes testou [. Dans ce cas, AND ( &&ou -a) a priorité sur OR ( ||ou -o). Merci au commentaire de Stéphane Chazelas pour avoir clarifié ce point.
  • Il semble que dans les langages C et C, la &&priorité soit supérieure à ||ce qui est probablement pourquoi vous vous attendiez à ce que votre construction d'origine se comporte comme

    true || (echo aaa && echo bbb). 

    Cependant, ce n'est pas le cas avec Bash, dans lequel les deux opérateurs ont la même priorité. C'est pourquoi Bash analyse votre expression à l'aide de la règle de gauche. Merci au commentaire de Kevin pour avoir soulevé cette question.

  • Il peut également y avoir des cas où les 3 expressions sont évaluées. Si la première commande renvoie un état de sortie différent de zéro, ||elle ne court-circuitera pas et exécutera la deuxième commande. Si la deuxième commande revient avec un état de sortie égal à zéro, &&elle ne sera pas court-circuitée également et la troisième commande sera exécutée. Merci au commentaire d'Ignacio Vazquez-Abrams pour avoir soulevé cette question.

Joseph R.
la source
26
Une petite remarque pour ajouter un peu plus de confusion: bien que les opérateurs &&et ||shell en tant que cmd1 && cmd2 || cmd3aient la même priorité, les commandes &&in ((...))et [[...]]ont la priorité sur ||( ((a || b && c))est ((a || (b && c)))). Même chose pour -a/ -odans test/ [et findet &/ |dans expr.
Stéphane Chazelas
16
Dans les langages de type C, la &&priorité est supérieure à ||celle du comportement attendu du PO. Les utilisateurs imprudents habitués à ce type de langage risquent de ne pas se rendre compte qu’ils ont la même priorité dans bash. Il peut donc être intéressant de préciser qu’ils ont la même priorité dans bash.
Kevin
Notez également qu'il existe des situations dans lesquelles les trois commandes peuvent être exécutées, c'est-à-dire si la commande du milieu peut renvoyer true ou false.
Ignacio Vazquez-Abrams
@ Kevin Veuillez vérifier la réponse modifiée.
Joseph R.
1
Aujourd'hui, pour moi, le meilleur hit de Google concernant la "priorité des opérateurs bash" est tldp.org/LDP/abs/html/opprecedence.html ... qui affirme que && a une priorité plus élevée que ||. Pourtant @JosephR. est clairement juste de facto et de jure aussi. Dash se comporte de la même manière et recherche "la précédence" dans pubs.opengroup.org/onlinepubs/009695399/utilities/… , je vois que c'est une exigence POSIX, nous pouvons donc en dépendre. Tout ce que j'ai trouvé pour signaler un bug, c’est l’adresse électronique de l’auteur, qui a sûrement été détruite par un spam pendant les six dernières années. Je vais essayer quand même ...
Martin Dorey
62

Si vous souhaitez que plusieurs éléments dépendent de votre condition, regroupez-les:

true || { echo aaa && echo bbb; }

Cela n'imprime rien, alors que

true && { echo aaa && echo bbb; }

imprime les deux chaînes.


La raison pour laquelle cela se produit est beaucoup plus simple que ne le prétend Joseph. Rappelez-vous ce que Bash fait avec ||et &&. Tout dépend du statut de retour de la commande précédente. Une façon littérale de regarder votre commande brute est la suivante:

( true || echo aaa ) && echo bbb

La première commande ( true || echo aaa) se termine avec 0.

$ true || echo aaa; echo $?
0
$ true && echo aaa; echo $?
aaa
0

$ false && echo aaa; echo $?
1
$ false || echo aaa; echo $?
aaa
0
Oli
la source
7
+1 Pour atteindre le résultat souhaité du PO. Notez que les parenthèses que vous avez placées (true || echo aaa) && echo bbbsont exactement ce que je compose.
Joseph R.
1
Heureux de voir @ Oli dans cette partie du monde.
Braiam
7
Notez la demi-colonne ;à la toute fin. Sans cela, ça ne marchera pas!
Serge Stroobandt
2
Cela me posait des problèmes car il me manquait le point-virgule final après la dernière commande (par exemple, echo bbb;dans les premiers exemples). Une fois que j'ai compris que cela me manquait, cette réponse était exactement ce que je cherchais. +1 pour m'aider à comprendre comment accomplir ce que je voulais!
Doktor
5
Il vaut la peine de lire pour quiconque confondu avec (...)et {...}notation: manuel de regroupement de commande
ANTARA
16

Les opérateurs &&et ne|| sont pas des remplacements inline exacts pour if-then-else. Bien que utilisés avec précaution, ils peuvent accomplir à peu près la même chose.

Un seul test est simple et sans ambiguïté ...

[[ A == A ]]  && echo TRUE                          # TRUE
[[ A == B ]]  && echo TRUE                          # 
[[ A == A ]]  || echo FALSE                         # 
[[ A == B ]]  || echo FALSE                         # FALSE

Cependant, essayer d'ajouter plusieurs tests peut donner des résultats inattendus ...

[[ A == A ]]  && echo TRUE   || echo FALSE          # TRUE  (as expected)
[[ A == B ]]  && echo TRUE   || echo FALSE          # FALSE (as expected)
[[ A == A ]]  || echo FALSE  && echo TRUE           # TRUE  (as expected)
[[ A == B ]]  || echo FALSE  && echo TRUE           # FALSE TRUE   (huh?)

Pourquoi les échos FALSE et TRUE sont-ils tous les deux ?

Ce qui se passe ici, c'est que nous ne le réalisons pas &&et que nous sommes des ||opérateurs surchargés qui agissent différemment entre crochets de test conditionnels [[ ]]et dans la liste AND et OR (exécution conditionnelle) que nous avons ici.

De la page de manuel bash (modifiée) ...

Listes

Une liste est une séquence d'un ou de plusieurs pipelines séparés par l'un des opérateurs;, &, &&, ou ││ et éventuellement terminés par l'un des;;, ou. De ces opérateurs de liste, && et ││ ont la même priorité, suivis de; et &, qui ont la même préséance.

Une séquence d'un ou plusieurs traits nouveaux peut apparaître dans une liste au lieu d'un point-virgule pour délimiter les commandes.

Si une commande est terminée par l'opérateur de contrôle &, le shell l'exécute en arrière-plan dans un sous-shell. Le shell n'attend pas la fin de la commande et le statut de retour est 0. Commandes séparées par un; sont exécutés séquentiellement; le shell attend que chaque commande se termine à son tour. Le statut de retour est le statut de sortie de la dernière commande exécutée.

Les listes AND et OR sont des séquences d'un ou plusieurs pipelines séparés par les opérateurs de contrôle && et, respectivement. Les listes AND et OR sont exécutées avec une associativité à gauche.

Une liste AND a la forme ...
command1 && command2
Command2 est exécuté si et seulement si command1 renvoie un état de sortie égal à zéro.

Une liste OR se présente sous la forme ...
command1 ││ command2
Command2 est exécuté si et seulement si command1 renvoie un état de sortie différent de zéro.

Le statut de retour des listes AND et OR est le statut de sortie de la dernière commande exécutée dans la liste.

Revenons à notre dernier exemple ...

[[ A == B ]]  || echo FALSE  && echo TRUE

[[ A == B ]]  is false

     ||       Does NOT mean OR! It means...
              'execute next command if last command return code(rc) was false'

 echo FALSE   The 'echo' command rc is always true
              (i.e. it successfully echoed the word "FALSE")

     &&       Execute next command if last command rc was true

 echo TRUE    Since the 'echo FALSE' rc was true, then echo "TRUE"

D'accord. Si c'est le cas, pourquoi l'avant-dernier exemple fait-il écho à quoi que ce soit?

[[ A == A ]]  || echo FALSE  && echo TRUE


[[ A == A ]]  is true

     ||       execute next command if last command rc was false.

 echo FALSE   Since last rc was true, shouldn't it have stopped before this?
                Nope. Instead, it skips the 'echo FALSE', does not even try to
                execute it, and continues looking for a `&&` clause.

     &&       ... which it finds here

 echo TRUE    ... so, since `[[ A == A ]]` is true, then it echos "TRUE"

Le risque d'erreurs logiques lors de l'utilisation de plus d'une &&ou ||dans une liste de commandes est assez élevé.

Recommandations

Un simple &&ou ||dans une liste de commandes fonctionne comme prévu, donc c'est assez sûr. S'il s'agit d'une situation dans laquelle vous n'avez pas besoin d'une clause else, les éléments suivants peuvent être plus clairs à suivre (les accolades sont nécessaires pour regrouper les 2 dernières commandes) ...

[[ $1 == --help ]]  && { echo "$HELP"; exit; }

Les opérateurs multiples &&et ||où chaque commande, à l'exception de la dernière, est un test (c'est-à-dire entre crochets [[ ]]), sont également sécurisés, car tous les opérateurs sauf le dernier se comportent comme prévu. Le dernier opérateur agit plutôt comme une clause thenou else.

DocSalvager
la source
0

Cela m'a aussi troublé, mais voici comment je pense à la façon dont Bash lit votre déclaration (comme il lit les symboles de gauche à droite):

  1. Symbole trouvé true. Cela devra être évalué une fois la fin de la commande atteinte. À ce stade, je ne sais pas s'il y a des arguments. Stocke la commande dans le tampon d'exécution.
  2. Symbole trouvé ||. La commande précédente est maintenant terminée, alors évaluez-la. Commande (tampon) en cours d' exécution: true. Résultat de l'évaluation: 0 (succès). Enregistrez le résultat 0 dans le registre "dernière évaluation". Considérons maintenant le symbole ||lui-même. Cela dépend du résultat de la dernière évaluation non-nulle. Vérifié le registre "dernière évaluation" et trouvé 0. Puisque 0 n'est pas différent de zéro, la commande suivante n'a pas besoin d'être évaluée.
  3. Symbole trouvé echo. Peut ignorer ce symbole, car la commande suivante n'a pas besoin d'être évaluée.
  4. Symbole trouvé aaa. Ceci est un argument de commande echo(3), mais puisque echo(3) n'a pas besoin d'être évalué, il peut être ignoré.
  5. Symbole trouvé &&. Cela dépend du résultat de la dernière évaluation étant zéro. Coche le registre "dernière évaluation" et trouve 0. Puisque 0 est égal à zéro, la commande suivante doit être évaluée.
  6. Symbole trouvé echo. Cette commande doit être évaluée une fois la fin de la commande atteinte, car la commande suivante a dû être évaluée. Stocke la commande dans le tampon d'exécution.
  7. Symbole trouvé bbb. Ceci est un argument pour command echo(6). Comme echoil fallait évaluer, ajouter bbbau tampon d'exécution.
  8. Atteint la fin de la ligne. La commande précédente est maintenant terminée et devait être évaluée. Commande (tampon) en cours d' exécution: echo bbb. Résultat de l'évaluation: 0 (succès). Enregistrez le résultat 0 dans le registre "dernière évaluation".

Et bien sûr, la dernière étape fait bbbécho à la console.

Kidburla
la source