Vorrang von Pipe (|) und logischem und (&&) in bash

17

Im klassischen Szenario mit Operator-Rangfolge haben Sie eine Zeile wie:

(cd ~/screenshots/ && ls screenshot* | head -n 5)

Und Sie wissen nicht, ob es analysiert ((A && B) | C)oder (A && B | C)...

Die fast offizielle Dokumentation, die hier zu finden ist, listet die Pipe nicht in der Liste auf, so dass ich nicht einfach in der Tabelle nachschauen kann.

Darüber hinaus dient Bash (nicht nur zum Ändern der Reihenfolge von Vorgängen, sondern erstellt auch eine Subshell. Daher bin ich mir nicht zu 100% sicher, ob diese Zeile der vorherigen Zeile entspricht:

((cd ~/screenshots/ && ls screenshot*) | head -n 5)

Wie kann man allgemein den AST einer Bash-Linie ermitteln? In Python habe ich eine Funktion, die mir den Baum gibt, so dass ich die Reihenfolge der Operationen leicht überprüfen kann.

Robert Vanden Eynde
quelle
1
Es könnte hilfreich sein zu wissen, dass |es sich nur um einen Anschluss handelt, der den LHS-Standard an den RHS-Standard anpasst. Also , wenn der cdBefehl in Ihrem Beispiel fehlschlägt, würde es keinen Ausgang senden head, aber headimmer noch in der Tat würde execute, analysiert das nichts, und keine Ausgabe zurück.
DopeGhoti
1
bashverwendet ein Yacc- Parser keine Ad-hoc-Sache; Wenn Sie das durchgehen yacc -v, erhalten Sie y.outputeine nette Grammatik, die das zeigt &&und ||Listen kombiniert, und Listen bestehen schließlich aus Pipelines (und nicht umgekehrt); tl; dr; A && B | Cist das gleiche wie A && { B | C; }, wie erwartet. Nehmen Sie keine Ausführungsreihenfolge zwischen Bund an C. Die Befehle in einer Pipeline werden parallel ausgeführt .
Mosvy
1
Beachten Sie, dass die "fast offizielle Dokumentation", auf die Sie verweisen, völlig irrelevant ist, da es sich um die Operatoren handelt, die in [...]Tests und $((...))arithmetischen Auswertungen verwendet werden. Insbesondere ||und &&wie bei Befehlslisten in der Shell-Sprache verwendet, haben sie den gleichen Vorrang , im Gegensatz zu den jeweiligen Operatoren in Coder bei der arithmetischen Auswertung (wo &&enger gebunden wird als ||).
Mosvy
@mosvy, dieses Dokument sagt nicht einmal aus, in welchem ​​Kontext die Tabelle angewendet wird, daher erscheint es auch in diesem Sinne weniger nützlich ...
ilkkachu

Antworten:

20
cd ~/screenshots/ && ls screenshot* | head -n 5

Dies ist äquivalent zu

cd ~/screenshots && { ls screenshot* | head -n 5 ; }

(Die geschweiften Klammern gruppieren Befehle ohne Unterschale ). Die Priorität von |ist somit höher (bindet enger) als &&und ||. Das ist,

A && B | C

und

A || B | C

bedeutet immer, dass nur der Ausgang von B an C gegeben werden soll . Sie können (...)oder verwenden { ... ; }, um Befehle zur Disambiguierung als eine Einheit zusammenzufügen:

{ A && B ; } | C
A && { B | C ; } # This is the default, but you might sometimes want to be explicit

Sie können dies mit verschiedenen Befehlen testen. Wenn du läufst

echo hello && echo world | tr a-z A-Z

dann wirst du bekommen

hello
WORLD

back: tr a-z A-ZGroßbuchstaben seiner Eingabe , und Sie können sehen, dass nur echo worldin es geleitet wurde, während es von selbst echo hellodurchlief.


Dies wird in der Shell-Grammatik definiert , obwohl dies nicht besonders klar ist: Die and_orProduktion (für &&/ ||) ist so definiert, dass sie aa pipelinein ihrem Körper hat, während sie pipelinenur enthält command, was nicht enthält and_or- nur die complete_commandProduktion kann erreichen and_or, und sie existiert nur am oberste Ebene und innerhalb der Körper von strukturellen Konstrukten wie Funktionen und Schleifen.

Sie können diese Grammatik manuell anwenden, um einen Analysebaum für einen Befehl zu erhalten, aber Bash stellt selbst nichts bereit. Ich kenne keine Shell, die über das hinausgeht, was für das eigene Parsen verwendet wird.

Die Shell-Grammatik hat viele Sonderfälle, die nur semi-formal definiert sind, und es kann eine ziemliche Aufgabe sein, die richtigen zu finden. Sogar Bash selbst hat es manchmal falsch verstanden , so dass die praktischen Aspekte und das Ideal unterschiedlich sein können.

Es gibt externe Parser, die versuchen, die Syntax abzugleichen und einen Baum zu erzeugen, und von diesen werde ich Morbig allgemein empfehlen , der versucht, der zuverlässigste zu sein.

Michael Homer
quelle
7

TL; DR : Listentrennzeichen wie ;. &, &&Und ||die Analyse , um zu entscheiden.

Das Bash-Handbuch sagt uns:

AND- und OR-Listen sind Sequenzen einer oder mehrerer Pipelines, die durch && und || getrennt sind Steueroperatoren.

Oder wie es das Wiki von Bash Hacker auf den Punkt gebracht hat

<PIPELINE1> && <PIPELINE2>

Es cd ~/screenshots/ && ls screenshot* | head -n 5 gibt also eine Pipeline - ls screenshot* | head -n 5und einen einfachen Befehl cd ~/screenshots/. Beachten Sie das laut Handbuch

Jeder Befehl in einer Pipeline wird als separater Prozess (dh in einer Subshell) ausgeführt.

Auf der anderen Seite (cd ~/screenshots/ && ls screenshot*) | head -n 5ist es anders - Sie haben eine Pipeline: Auf der linken Seite befindet sich eine Unterschale und auf der rechten Seite haben Sie eine head -n 5. In diesem Fall wäre es die OP-Notation(A && B) | C


Nehmen wir ein anderes Beispiel:

$ echo foo | false &&  echo 123 | tr 2 5
$

Hier haben wir eine Liste <pipeline1> && <pipeline2>. Da wir wissen, dass der Beendigungsstatus der Pipeline derselbe ist wie der des letzten Befehls und den falsenegativen Status (auch als fehlgeschlagen bezeichnet) zurückgibt, &&wird er nicht auf der rechten Seite ausgeführt.

$ echo foo | true &&  echo 123 | tr 2 5
153

Hier hat die linke Pipeline den Status "Erfolgreich beendet". Die rechte Pipeline wird ausgeführt und die Ausgabe wird angezeigt.


Beachten Sie, dass die Shell-Grammatik keine tatsächliche Ausführungsreihenfolge impliziert. Um eine Antwort von Gilles zu zitieren :

Weitergeleitete Befehle werden gleichzeitig ausgeführt. Wenn Sie ps | ausführen grep…, es ist das Glück der Auslosung (oder eine Frage der Funktionsweise der Shell in Kombination mit der Feinabstimmung des Schedulers tief im Kern), ob ps oder grep zuerst gestartet werden und auf jeden Fall werden sie fortgesetzt gleichzeitig ausführen.

Und aus dem Bash-Handbuch:

AND- und OR-Listen werden mit linker Assoziativität ausgeführt.

Basierend auf , dass in cd ~/screenshots/ && ls screenshot* | head -n 5der cd ~/screenshots/würde zuerst ausgeführt werden, ls screenshot* | head -n 5wenn die vorherige Befehl erfolgreich ist , aber head -n 5kann ein erster Prozess als gelaicht eher sein , lsda sie in einer Pipeline sind.

Sergiy Kolodyazhnyy
quelle
1
Ich verstehe nicht, wo die Frage nach der Reihenfolge fragt, in der die Dinge erzeugt werden.
Michael Homer
"Es gibt keinen Vorrang, von dem man zuerst spawnt, cd ~/screenshots/ && ls screenshot*oder head -n 5" Nun ja, das ist der Fall bei ((cd ~/screenshots/ && ls screenshot*) | head -n 5). Aber es sagt nichts über den zweideutigen Fall aus, cd ~/screenshots/ && ls screenshot* | head -n 5der der Punkt der Frage zu sein scheint (mit oder ohne umgebende Klammer).
Ilkkachu
@ilkkachu Richtig, aber die folgenden Sätze berühren das. Müsste cd ~/screenshots/ && ls screenshot* zuerst verarbeitet werden, da &&Listen in der Rangfolge höher sind (zumindest basierend auf der Antwort von l0b0). Wo sollte ich die Antwort verbessern?
Sergiy Kolodyazhnyy
@SergiyKolodyazhnyy, na ja, angesichts der Tatsache, dass die Frage beides hat (cd && ls | head)und ((cd && ls) | head), könnte es gut sein, genau zu sagen, welche Sie meinen.
Ilkkachu
@ilkkachu OK, ich werde dies vorerst löschen und es in der Zwischenzeit bearbeiten
Sergiy Kolodyazhnyy
3

Hier ist angegeben in bash(1):

SHELL GRAMMAR
[...]
   Pipelines
       A  pipeline  is  a sequence of one or more commands separated by one of
       the control operators | or |&.
[...]
   Lists
       A list is a sequence of one or more pipelines separated by one  of  the
       operators ;, &, &&, or ||, and optionally terminated by one of ;, &, or
       <newline>.

Trennt also &&Pipelines.

JoL
quelle
2

Du könntest es einfach versuchen echo hello && echo world | less. Sie werden sehen, dass dies |eine höhere Priorität hat (Eine Pipeline von Befehlen ist ein Befehl). Dort ist für dein 2. Beispiel NICHT dasselbe. Da es cdjedoch keine Ausgabe gibt, werden Sie keinen Unterschied feststellen können.

Strg-Alt-Delor
quelle