Vorrang der logischen Shell-Operatoren &&, ||

126

Ich versuche zu verstehen, wie die logische Operatorrangfolge in Bash funktioniert. Zum Beispiel hätte ich erwartet, dass der folgende Befehl nichts wiedergibt.

true || echo aaa && echo bbb

Entgegen meiner Erwartung bbbwird jedoch gedruckt.

Kann mir bitte jemand erklären, wie ich Compounded &&und ||Operatoren in Bash verstehen kann ?

Martin Vegter
quelle

Antworten:

127

In vielen Computersprachen sind Operatoren mit der gleichen Priorität linksassoziativ . Das heißt, in Abwesenheit von Gruppierungsstrukturen werden Operationen ganz links zuerst ausgeführt. Bash ist keine Ausnahme von dieser Regel.

Dies ist wichtig, weil in Bash &&und ||haben den gleichen Vorrang.

In Ihrem Beispiel wird also zuerst die Operation ( ||) ganz links ausgeführt:

true || echo aaa

Da dies trueoffensichtlich zutrifft, wird der ||Operator kurzgeschlossen, und die gesamte Aussage wird als wahr betrachtet, ohne dass eine erwartungsgemäße Bewertung erforderlich ist echo aaa. Jetzt muss noch die Operation ganz rechts ausgeführt werden:

(...) && echo bbb

Da die erste Operation als wahr ausgewertet wurde (dh einen Exit-Status von 0 hatte), ist dies so, als würden Sie sie ausführen

true && echo bbb

also &&wird der nicht kurzschließen, weshalb du bbbecho siehst .

Sie würden das gleiche Verhalten mit bekommen

false && echo aaa || echo bbb

Notizen basierend auf den Kommentaren

  • Sie sollten beachten, dass die Linksassoziativitätsregel nur befolgt wird, wenn beide Operatoren dieselbe Priorität haben. Dies ist nicht der Fall, wenn Sie diese Operatoren in Verbindung mit Schlüsselwörtern wie [[...]]oder verwenden ((...))oder die Operatoren -ound -aals Argumente für die Befehle testoder verwenden [. In solchen Fällen hat UND ( &&oder -a) Vorrang vor ODER ( ||oder -o). Vielen Dank an Stephane Chazelas Kommentar zur Klärung dieses Punktes.
  • Es scheint, dass in C und C-ähnlichen Sprachen &&eine höhere Priorität vorliegt, als ||dies wahrscheinlich der Grund ist, warum Sie erwartet haben, dass sich Ihr ursprüngliches Konstrukt so verhält

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

    Dies ist jedoch bei Bash nicht der Fall, bei dem beide Operatoren dieselbe Priorität haben. Aus diesem Grund analysiert Bash Ihren Ausdruck mithilfe der Linksassoziativitätsregel. Vielen Dank an Kevins Kommentar, der dies angesprochen hat.

  • Es kann auch Fälle geben, in denen alle drei Ausdrücke ausgewertet werden. Wenn der erste Befehl einen Beendigungsstatus ungleich Null zurückgibt, schließt der ||nicht kurz und führt den zweiten Befehl aus. Wenn der zweite Befehl mit einem Ausgangsstatus von Null zurückkehrt, &&wird der Befehl ebenfalls nicht kurzgeschlossen und der dritte Befehl wird ausgeführt. Vielen Dank an Ignacio Vazquez-Abrams Kommentar, der dies angesprochen hat.

Joseph R.
quelle
26
Ein kleiner Hinweis, um ein wenig mehr Verwirrung zu stiften: Während die Operatoren &&und die ||Shell-Operatoren cmd1 && cmd2 || cmd3dieselbe Priorität haben, hat das &&In ((...))und [[...]]Vorrang vor ||( ((a || b && c))is ((a || (b && c)))). Gleiches gilt für -a/ -oin test/ [und findund &/ |in expr.
Stéphane Chazelas
16
Hat in c-ähnlichen Sprachen &&eine höhere Priorität als ||, sodass das erwartete Verhalten des OP eintreten würde. Unvorsichtige Benutzer, die an solche Sprachen gewöhnt sind, erkennen möglicherweise nicht, dass sie in bash die gleiche Priorität haben. Daher sollte expliziter darauf hingewiesen werden, dass sie in bash die gleiche Priorität haben.
Kevin
Beachten Sie auch, dass es Situationen gibt, in denen alle drei Befehle ausgeführt werden können, dh wenn der mittlere Befehl entweder true oder false zurückgeben kann.
Ignacio Vazquez-Abrams
@ Kevin Bitte überprüfe die bearbeitete Antwort.
Joseph R.
1
Heute ist tldp.org/LDP/abs/html/opprecedence.html für mich der Top-Google-Hit für "Bash-Operator-Rangfolge", der besagt , dass && eine höhere Rangfolge hat als ||. Noch @JosephR. ist de facto und de jure eindeutig richtig. Dash verhält sich genauso und sucht unter pubs.opengroup.org/onlinepubs/009695399/utilities/… nach "Priorität" . Ich sehe, dass dies eine POSIX-Anforderung ist, sodass wir uns darauf verlassen können. Alles, was ich für die Fehlerberichterstattung gefunden habe, war die E-Mail-Adresse des Autors, die in den letzten sechs Jahren mit Sicherheit zu Tode gespammt wurde. Ich werde es trotzdem versuchen ...
Martin Dorey
62

Wenn Sie möchten, dass mehrere Dinge von Ihrem Zustand abhängen, gruppieren Sie sie:

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

Das druckt nichts, solange

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

druckt beide Zeichenketten.


Der Grund, warum dies geschieht, ist viel einfacher, als Joseph ausmacht. Denken Sie daran, was Bash mit ||und macht &&. Es dreht sich alles um den Rückgabestatus des vorherigen Befehls. Eine wörtliche Sichtweise auf Ihren Rohbefehl lautet:

( true || echo aaa ) && echo bbb

Der erste Befehl ( true || echo aaa) wird mit beendet 0.

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

$ false && echo aaa; echo $?
1
$ false || echo aaa; echo $?
aaa
0
Oli
quelle
7
+1 Für das Erreichen des beabsichtigten Ergebnisses des OP. Beachten Sie, dass die Klammern, in die Sie eingefügt haben, (true || echo aaa) && echo bbbgenau das sind, was ich gerade mache.
Joseph R.
1
Schön, @Oli auf dieser Seite der Welt zu sehen.
Braiam
7
Beachten Sie die halbe Spalte ganz ;am Ende. Ohne das geht es nicht!
Serge Stroobandt
2
Ich hatte Probleme damit, weil mir nach dem letzten Befehl das nachgestellte Semikolon fehlte (z. B. echo bbb;in den ersten Beispielen). Als ich herausfand, dass mir das fehlte, war diese Antwort genau das, wonach ich suchte. +1 für die Hilfe, um herauszufinden, wie ich das erreichen kann, was ich wollte!
Doktor J
5
(...){...}
ANTARA
16

Die Operatoren &&und ||sind keine exakten Inline-Ersetzungen für if-then-else. Bei vorsichtiger Verwendung können sie jedoch fast dasselbe erreichen.

Ein einziger Test ist unkompliziert und eindeutig ...

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

Der Versuch, mehrere Tests hinzuzufügen, kann jedoch zu unerwarteten Ergebnissen führen ...

[[ 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?)

Warum werden sowohl FALSE als auch TRUE wiederholt?

Was hier passiert, ist, dass wir das nicht erkannt haben &&und ||überladene Operatoren sind, die in bedingten Testklammern anders agieren [[ ]]als in der UND- und ODER-Liste (bedingte Ausführung), die wir hier haben.

Aus der Bash-Manpage (bearbeitet) ...

Listen

Eine Liste ist eine Folge von einer oder mehreren Pipelines, die durch einen der Operatoren;, &, && oder ││ getrennt und optional durch eines von;, &, oder abgeschlossen sind. Von diesen Listenoperatoren haben && und ││ die gleiche Priorität, gefolgt von; und &, die den gleichen Vorrang haben.

Eine Folge von einer oder mehreren Zeilenumbrüchen kann in einer Liste anstelle eines Semikolons zur Begrenzung von Befehlen angezeigt werden.

Wenn ein Befehl vom Steueroperator & beendet wird, führt die Shell den Befehl im Hintergrund in einer Subshell aus. Die Shell wartet nicht auf den Abschluss des Befehls und der Rückgabestatus ist 0. Befehle durch ein; getrennt. werden nacheinander ausgeführt; Die Shell wartet darauf, dass die einzelnen Befehle der Reihe nach beendet werden. Der Rückgabestatus ist der Beendigungsstatus des zuletzt ausgeführten Befehls.

AND- und OR-Listen sind Sequenzen einer oder mehrerer Pipelines, die durch die Steueroperatoren && bzw. ││ getrennt sind. AND- und OR-Listen werden mit linker Assoziativität ausgeführt.

Eine UND-Liste hat die Form ...
command1 && command2
Befehl2 wird nur dann ausgeführt, wenn Befehl1 den Beendigungsstatus Null zurückgibt.

Eine ODER-Liste hat die Form ...
command1 ││ command2
Befehl2 wird genau dann ausgeführt, wenn Befehl1 einen Exit-Status ungleich Null zurückgibt.

Der Rückgabestatus von AND- und OR-Listen ist der Exit-Status des zuletzt ausgeführten Befehls in der Liste.

Zurück zu unserem letzten Beispiel ...

[[ 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"

Okay. Wenn das richtig ist, warum gibt das vorletzte Beispiel dann überhaupt etwas wieder?

[[ 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"

Das Risiko von Logikfehlern bei der Verwendung mehrerer Befehle &&oder ||in einer Befehlsliste ist sehr hoch.

Empfehlungen

Eine einzelne &&oder ||in einer Befehlsliste funktioniert wie erwartet, ist also ziemlich sicher. In Situationen, in denen Sie keine else-Klausel benötigen, kann Folgendes klarer sein (die geschweiften Klammern sind erforderlich, um die letzten zwei Befehle zu gruppieren) ...

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

Multiple &&und ||Operatoren, bei denen jeder Befehl mit Ausnahme des letzten ein Test ist (dh in eckigen Klammern [[ ]]), sind normalerweise ebenfalls sicher, da sich alle Operatoren außer dem letzten wie erwartet verhalten. Der letzte Operator verhält sich eher wie eine thenor- elseKlausel.

DocSalvager
quelle
0

Das hat mich auch verwirrt, aber hier ist, wie ich über die Art und Weise denke, wie Bash Ihre Aussage liest (wie sie die Symbole von links nach rechts liest):

  1. Symbol gefunden true. Dies muss ausgewertet werden, sobald das Ende des Befehls erreicht ist. Zu diesem Zeitpunkt wissen Sie nicht, ob es irgendwelche Argumente gibt. Befehl im Ausführungspuffer speichern.
  2. Symbol gefunden ||. Der vorherige Befehl ist jetzt abgeschlossen. Bewerten Sie ihn. Befehl (Puffer) wird ausgeführt: true. Ergebnis der Bewertung: 0 (dh Erfolg). Speichern Sie das Ergebnis 0 im Register "Letzte Bewertung". Betrachten Sie nun das Symbol ||selbst. Dies hängt davon ab, ob das Ergebnis der letzten Auswertung nicht Null ist. Überprüftes Register "Letzte Auswertung" und gefunden 0. Da 0 nicht ungleich Null ist, muss der folgende Befehl nicht ausgewertet werden.
  3. Symbol gefunden echo. Dieses Symbol kann ignoriert werden, da der folgende Befehl nicht ausgewertet werden musste.
  4. Symbol gefunden aaa. Dies ist ein Argument für Befehl echo(3), aber da echo(3) nicht ausgewertet werden musste, kann es ignoriert werden.
  5. Symbol gefunden &&. Dies hängt davon ab, ob das Ergebnis der letzten Auswertung Null ist. Überprüftes Register "Letzte Auswertung" und gefunden 0. Da 0 Null ist, muss der folgende Befehl ausgewertet werden.
  6. Symbol gefunden echo. Dieser Befehl muss ausgewertet werden, sobald das Ende des Befehls erreicht ist, da der folgende Befehl ausgewertet werden musste. Befehl im Ausführungspuffer speichern.
  7. Symbol gefunden bbb. Dies ist ein Argument für Befehl echo(6). Da echoausgewertet werden musste, fügen Sie bbbden Ausführungspuffer hinzu.
  8. Zeilenende erreicht. Der vorherige Befehl ist jetzt abgeschlossen und musste ausgewertet werden. Befehl (Puffer) wird ausgeführt: echo bbb. Ergebnis der Bewertung: 0 (dh Erfolg). Speichern Sie das Ergebnis 0 im Register "Letzte Bewertung".

Und natürlich wird der letzte Schritt bbbauf der Konsole wiedergegeben.

Kidburla
quelle