web-dev-qa-db-fra.com

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

J'essaie de comprendre comment fonctionne la priorité de l'opérateur logique dans bash. Par exemple, je me serais attendu à ce que la commande suivante ne fasse écho à rien.

true || echo aaa && echo bbb

Cependant, contrairement à mes attentes, bbb est imprimé.

Quelqu'un peut-il expliquer, comment puis-je donner un sens à _ && et || opérateurs en bash?

135
Martin Vegter

Dans de nombreux langages informatiques, les opérateurs ayant la même priorité sont associatifs à gauche . Autrement dit, en l'absence de structures de regroupement, les opérations les plus à gauche sont exécutées en premier. Bash est sans 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 (||) s'effectue d'abord:

true || echo aaa

Puisque true est évidemment vrai, le || court-circuits de l'opérateur et la déclaration entière est considérée comme vraie sans qu'il soit nécessaire d'évaluer echo aaa comme vous vous en doutez. Reste maintenant à faire l'opération la plus à droite:

(...) && echo bbb

Depuis la première opération évaluée à vrai (c.-à-d. Avait un état de sortie 0), c'est comme si vous exécutiez

true && echo bbb

alors le && ne court-circuite pas, c'est pourquoi vous voyez bbb en écho.

Vous obtiendriez le même comportement avec

false && echo aaa || echo bbb

Notes basées sur les commentaires

  • Vous devez noter que la règle d'associativité gauche est uniquement suivie lorsque les deux opérateurs ont la priorité la même. Ce n'est pas le cas lorsque vous utilisez ces opérateurs conjointement avec des mots clés tels que [[...]] ou ((...)) ou utilisez le -o et -a opérateurs comme arguments des test ou [ commandes. Dans de tels cas, ET (&& ou -a) a priorité sur OR (|| ou -o). Merci au commentaire de Stéphane Chazelas d'avoir clarifié ce point.
  • Il semble que en C et langages de type C && a une priorité plus élevée que || c'est probablement pourquoi vous vous attendiez à ce que votre construction d'origine se comporte comme

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

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

  • Il peut également y avoir des cas où les expressions tous les 3 sont évaluées. Si la première commande renvoie un état de sortie différent de zéro, le || ne court-circuite pas et continue d'exécuter la deuxième commande. Si la deuxième commande retourne avec un état de sortie nul, alors le && ne court-circuite pas aussi bien et la troisième commande sera exécutée. Merci au commentaire d'Ignacio Vazquez-Abrams d'avoir soulevé cette question.

138
Joseph R.

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

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

Cela n'imprime rien,

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

imprime les deux chaînes.


La raison pour laquelle cela se produit est beaucoup plus simple que Joseph ne le prétend. 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
68
Oli

Le && et || les opérateurs sont ( pas remplacements exacts en ligne pour if-then-else. Bien que s'ils sont utilisés avec soin, 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, tenter 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 FALSE et TRUE sont-ils répercutés?

Ce qui se passe ici, c'est que nous n'avons pas réalisé que && et || sont des opérateurs surchargés qui agissent différemment à l'intérieur des crochets de test conditionnels [[ ]] que dans les listes ET et OR (exécution conditionnelle) que nous avons ici.

Depuis la page de manuel bash (éditée) ...

Listes

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

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

Si une commande est interrompue par l'opérateur de contrôle &, le shell exécute la commande en arrière-plan dans un sous-shell. Le shell n'attend pas la fin de la commande et l'état 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.

ET et OR les listes sont des séquences d'un ou plusieurs pipelines séparés respectivement par les opérateurs de contrôle && et respectively. ET et OR les listes sont exécutées avec l'associativité gauche) .

Une liste ET a la forme ...
command1 && command2
La commande2 est exécutée si et seulement si la commande1 renvoie un état de sortie nul.

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

L'état de retour de AND et OR lists est l'état 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 exact, pourquoi l'avant-dernier exemple fait-il écho à quelque chose?

[[ 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 plusieurs && ou || dans une liste de commandes est assez élevé.

Recommandations

Un seul && ou || dans une liste de commandes fonctionne comme prévu et est donc assez sûr. S'il s'agit d'une situation où vous n'avez pas besoin d'une clause else, quelque chose comme ce qui suit peut être plus clair à suivre (les accolades sont nécessaires pour regrouper les 2 dernières commandes) ...

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

Plusieurs && et || opérateurs, où chaque commande, à l'exception de la dernière, est un test (c'est-à-dire entre crochets [[ ]]), sont généralement aussi sûrs que tous, mais le dernier opérateur se comporte comme prévu. Le dernier opérateur agit plus comme une clause then ou else.

20
DocSalvager

J'ai également été confus par cela, mais voici comment je pense à la façon dont Bash lit votre déclaration (car elle 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 contient des arguments. Stocker 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 (c'est-à-dire succès). Enregistrer le résultat 0 dans le registre "dernière évaluation". Considérez maintenant le symbole || lui-même. Cela dépend du résultat de la dernière évaluation étant non nul. Registre "dernière évaluation" vérifié 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. C'est un argument pour commander echo (3), mais comme 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 nul. Registre "dernière évaluation" vérifié et trouvé 0. Puisque 0 est 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. Stocker la commande dans le tampon d'exécution.
  7. Symbole trouvé bbb. C'est un argument pour commander echo (6). Puisque echo devait être évalué, ajoutez bbb au tampon d'exécution.
  8. Fin de ligne atteinte. 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 (c'est-à-dire succès). Enregistrer le résultat 0 dans le registre "dernière évaluation".

Et bien sûr, la dernière étape fait en sorte que bbb soit répercuté sur la console.

0
Kidburla