it-swarm.com.de

Vorrang der Shell-Logikoperatoren &&, ||

Ich versuche zu verstehen, wie die Priorität des logischen Operators in bash funktioniert. Zum Beispiel hätte ich erwartet, dass der folgende Befehl nichts wiedergibt.

true || echo aaa && echo bbb

Entgegen meiner Erwartung wird jedoch bbb gedruckt.

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

135
Martin Vegter

In vielen Computersprachen sind Operatoren mit derselben Priorität linksassoziativ . Das heißt, wenn keine Gruppierungsstrukturen vorhanden sind, werden Operationen ganz links zuerst ausgeführt. Bash ist keine Ausnahme zu dieser Regel.

Dies ist wichtig, da in Bash && Und || Dieselbe Priorität haben.

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

true || echo aaa

Da true offensichtlich wahr ist, schließt der Operator || Kurz und die gesamte Aussage wird als wahr angesehen, ohne dass echo aaa Wie erwartet ausgewertet werden muss. Jetzt bleibt die Operation ganz rechts:

(...) && echo bbb

Da die erste Operation mit true bewertet wurde (d. H. Einen Exit-Status von 0 hatte), ist es so, als würden Sie sie ausführen

true && echo bbb

daher wird && nicht kurzgeschlossen, weshalb bbb wiedergegeben wird.

Sie würden das gleiche Verhalten mit bekommen

false && echo aaa || echo bbb

Anmerkungen basierend auf den Kommentaren

  • Sie sollten beachten, dass die Linksassoziativitätsregel nur lautet, wenn beide Operatoren gleich Vorrang haben. Dies ist nicht der Fall, wenn Sie diese Operatoren in Verbindung mit Schlüsselwörtern wie [[...]] Oder ((...)) Verwenden oder die Operatoren -o Und -a Als Argumente für die verwenden test oder [ Befehle. In solchen Fällen hat AND (&& Oder -a Vorrang vor OR (|| Oder -o). Danke zu Stephane Chazelas 'Kommentar zur Klarstellung dieses Punktes.
  • Es scheint, dass in C und C-ähnlichen Sprachen && Eine höhere Priorität hat als ||, Was 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. Deshalb 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 3 Ausdrücke ausgewertet werden. Wenn der erste Befehl einen Exit-Status ungleich Null zurückgibt, schließt || Nicht kurz und führt den zweiten Befehl aus. Wenn der zweite Befehl mit einem Exit-Status von Null zurückkehrt, wird auch && Nicht kurzgeschlossen und der dritte Befehl wird ausgeführt. Vielen Dank an Ignacio Vazquez-Abrams 'Kommentar, der dies angesprochen hat.

138
Joseph R.

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

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

Das druckt dabei nichts

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

druckt beide Zeichenfolgen.


Der Grund, warum dies geschieht, ist viel einfacher als Joseph es ausmacht. Denken Sie daran, was Bash mit || Und && Macht. Es geht nur um den Rückgabestatus des vorherigen Befehls. Eine wörtliche Sichtweise auf Ihren Rohbefehl ist:

( true || echo aaa ) && echo bbb

Der erste Befehl (true || echo aaa) Wird mit 0 Beendet.

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

$ false && echo aaa; echo $?
1
$ false || echo aaa; echo $?
aaa
0
68
Oli

Das && und || Operatoren sind nicht exakte Inline-Ersetzungen für if-then-else. Bei sorgfältiger Anwendung 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 wiedergegeben?

Was hier passiert ist, dass wir nicht bemerkt haben, dass && und || sind überladene Operatoren, die sich in bedingten Testklammern unterschiedlich verhalten [[ ]] als in der Liste AND und OR (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 eine der;, &, oder beendet 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 angezeigt werden, um Befehle abzugrenzen.

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 getrennt durch a; werden nacheinander ausgeführt; Die Shell wartet darauf, dass jeder Befehl nacheinander beendet wird. 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 && und ││ 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 OR 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 in der Liste ausgeführten Befehls.

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 Verwendung von mehr als einem && oder || in einer Befehlsliste ist ziemlich hoch.

Empfehlungen

Ein einzelnes && oder || in einer Befehlsliste funktioniert wie erwartet und ist daher ziemlich sicher. Wenn Sie in einer Situation keine else-Klausel benötigen, kann Folgendes klarer sein (die geschweiften Klammern sind erforderlich, um die letzten beiden Befehle zu gruppieren) ...

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

Mehrere && und || Operatoren, wobei jeder Befehl mit Ausnahme des letzten ein Test ist (d. h. in Klammern [[ ]]) sind normalerweise auch sicher, da sich alle außer dem letzten Operator wie erwartet verhalten. Der letzte Operator verhält sich eher wie eine then - oder else -Klausel.

20
DocSalvager

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

  1. Gefundenes Symbol true. Dies muss ausgewertet werden, sobald das Ende des Befehls erreicht ist. Zu diesem Zeitpunkt weiß ich nicht, ob es irgendwelche Argumente gibt. Befehl im Ausführungspuffer speichern.
  2. Gefundenes Symbol ||. Der vorherige Befehl ist jetzt abgeschlossen. Bewerten Sie ihn daher. Befehl (Puffer) wird ausgeführt: true. Ergebnis der Bewertung: 0 (d. H. Erfolg). Speichern Sie das Ergebnis 0 im Register "Letzte Auswertung". Betrachten Sie nun das Symbol || selbst. Dies hängt davon ab, dass das Ergebnis der letzten Bewertung ungleich Null ist. Das Register "Letzte Auswertung" wurde überprüft und 0 gefunden. Da 0 nicht ungleich Null ist, muss der folgende Befehl nicht ausgewertet werden.
  3. Gefundenes Symbol echo. Kann dieses Symbol ignorieren, da der folgende Befehl nicht ausgewertet werden musste.
  4. Gefundenes Symbol aaa. Dies ist ein Argument für den Befehl echo (3), aber da echo (3) nicht ausgewertet werden musste, kann es ignoriert werden.
  5. Gefundenes Symbol &&. Dies hängt davon ab, dass das Ergebnis der letzten Auswertung Null ist. Das Register "Letzte Auswertung" wurde überprüft und 0 gefunden. Da 0 Null ist, muss der folgende Befehl ausgewertet werden.
  6. Gefundenes Symbol 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. Gefundenes Symbol bbb. Dies ist ein Argument für den Befehl echo (6). Da echo ausgewertet werden musste, fügen Sie bbb zum 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 (d. H. Erfolg). Speichern Sie das Ergebnis 0 im Register "Letzte Auswertung".

Und natürlich bewirkt der letzte Schritt, dass bbb an die Konsole zurückgegeben wird.

0
Kidburla