Mehrere Befehle und Subshell-Ausführung nach Pipeline

Mehrere Befehle und Subshell-Ausführung nach Pipeline

Ok, ich weiß, dass in Bash (standardmäßig, ohne aktivierte Bash-Option „lastpipe“) jede nach einer Pipeline zugewiesene Variable tatsächlich in einer Subshell ausgeführt wird und die Variable selbst nach der Ausführung der Subshell stirbt. Sie bleibt für den übergeordneten Prozess nicht verfügbar. Aber bei einigen Tests bin ich auf dieses Verhalten gestoßen:

A) Der zweite Befehl (a=2) weist den Wert zu und gibt zurück:

[root@centos01]# a=1; a=2; a=10 | echo $a
2

B) Der dritte Befehl (a=10) weist den Wert zu und gibt zurück:

[root@centos01]# a=1; a=2; a=10; a=20 | echo $a
10

C) Der vierte Befehl (a=20) weist den Wert zu und gibt zurück:

[root@centos01]# a=1; a=2; a=10; a=20; touch fileA.txt | echo $a
20

Also:

  • Warum wird die letzte Variablenzuweisung in der Befehlssequenz nicht immer ausgeführt? (oder wenn doch, warum wird sie nicht von der Subshell abgefangen und per Echo-Befehl zurückgegeben?)

  • Im Test C hat der Befehl „touch“ tatsächlich die Datei „fileA.txt“ im Verzeichnis erstellt. Warum hat also der letzte Befehl in der Sequenz für die Variablenzuweisung in Schritt A und Satz B nicht funktioniert? Kennt jemand die technische Erklärung dafür?

Antwort1

Um uns zunächst auf einige Namen zu einigen. So interpretiert die Shell Ihre Eingabe:

$ a=1; a=2; a=10 | echo $a
  ^^^  ^^^  ^^^^^^^^^^^^^^
    \    \         \_ Pipeline
     \    \_ Simple command
      \_ Simple command

Die Pipeline besteht aus zwei einfachen Befehlen:

$ a=10 | echo $a
  ^^^^   ^^^^^^^
     \       \_ Simple command
      \_ Simple command

(Beachten Sie, dass es zwar nicht klar im Bash-Handbuch angegeben ist, aberPOSIX-Shell-Grammatikermöglicht die Bildung eines einfachen Befehls aus einer bloßen Variablenzuweisung).

a=1;und a=2;sind nicht Teil einer Pipeline. Ein ;würde eine Pipeline beenden, außer wenn es als Teil eineszusammengesetzter Befehl. Wie zum Beispiel:

{ a=1; a=2; a=10; } | echo $a

In Ihrem Beispiel werden a=10und echo $aausgeführt inzwei verschiedene, unabhängige Subshell-Umgebungen 1 , die beide als Kopien der Hauptumgebung erstellt werden. Subshells dürfen ihre übergeordnete Ausführungsumgebung 2 nicht ändern . Zitieren Sie die relevantenPOSIX-Abschnitt:

Eine Subshell-Umgebung soll als Duplikat der Shell-Umgebung erstellt werden. [...] Änderungen, die an der Subshell-Umgebung vorgenommen werden, dürfen sich nicht auf die Shell-Umgebung auswirken.

Und

Darüber hinaus befindet sich jeder Befehl einer Pipeline mit mehreren Befehlen in einer Subshell-Umgebung. Als Erweiterung können jedoch alle oder einzelne Befehle einer Pipeline in der aktuellen Umgebung ausgeführt werden. Alle anderen Befehle müssen in der aktuellen Shell-Umgebung ausgeführt werden.

Während also alle Befehle in Ihren Beispielen tatsächlich ausgeführt werden, haben die Zuweisungen im linken Teil Ihrer Pipelines keine sichtbaren Auswirkungen: Sie ändern nur die Kopien in aihren jeweiligen Subshell-Umgebungen, die verloren gehen, sobald die Subshells beendet werden.

Die einzige Möglichkeit, wie die Subshells an den beiden Enden einer Pipe direkt miteinander interagieren können, ist über die Pipe selbst – die Standardausgabe der linken Seite ist mit der Standardeingabe der rechten Seite verbunden. Da a=10nichts über die Pipe gesendet wird, kann es auch nicht beeinflussen echo $a.


1 Wenn die lastpipeOption gesetzt ist (sie ist standardmäßig deaktiviert und kann mit dem shoptintegrierten Befehl aktiviert werden), kann Bash den letzten Befehl einer Pipeline in der aktuellen Shell ausführen. SieheRohrleitungenim Bash-Handbuch. Dies ist jedoch im Kontext Ihrer Frage nicht relevant.

2 Weitere Einzelheiten aus praktischer/historischer Sicht zu U&L finden Sie beispielsweise indiese AntwortZuWarum behält die zuletzt in einer POSIX-Shell-Skript-Pipeline ausgeführte Funktion die Variablenwerte nicht bei?

Antwort2

Bitte entschuldigen Sie mein Englisch, ich lerne es noch. Ich bin auch ein Bash-Anfänger, also korrigieren Sie bitte alle Fehler, die ich in meiner Antwort gemacht habe, danke.

Zunächst möchte ich auf einen Fehler hinweisen.

  • In a=10 | echo $aleiten Sie (mit dem Pipe-Operator ; |) a=10eine Verbindung zum Echo-Befehl weiter. Durch die Weiterleitung wird der stdoutBefehl a mit stdinBefehl2 verbunden, d. h command | command2. .

  • a=10ist eine Variablenzuweisung, ich gehe davon aus, dass es stdoutdafür keine gibt, da es kein Befehl ist. Wenn Sie eine Befehlsersetzung im Wert einer Variablenzuweisung durchführen, funktioniert dies nicht. Wenn ich Folgendes versuche:

    user@host$ a=$(b=10); echo $a
    

    gibt echo $aden Wert nicht zurück 10. Als ich es geändert habe zu

    user@host$ a=$(b=10; echo $b)
    

    ein Anruf von

    $ echo $a
    

    Daher gehe ich wahrscheinlich richtig davon aus 10, dass die Variablenzuweisung kein Befehl ist (selbst gemäß der manuellen Definition von Bash ist sie kein Befehl).

Zweitens echonimmt der Befehl keine Eingaben von entgegen stdin, sondern druckt seine Argumente aus.

user@host$ echo "I love linux" | echo

gibt nichts zurück. Sie können xargsden Befehl verwenden, um dies zu umgehen:

user@host$ echo "I love linux" | xargs echo

gibt zurück I love linux. Daher funktioniert die Weiterleitung nicht direkt auf echoBefehl, da die Argumente ausgedruckt werden und nicht stdin.

Nun zu Ihren Tests

  • Unter deinem Kommando

    user@host$ a=1; a=2; a=10 | echo $a
    

    der Variablen awird 1zunächst der Wert zugewiesen, dann wird der Wert der Variablen in geändert 2, beides in der aktuellen Shell-Umgebung. Befehle werden im Allgemeinen in einer Unter-Shell ausgeführt. a=10 | echo $aist eine Liste, also gleichwertig zu , (a=10 | echo $a)das in einer Unter-Shell ausgeführt wird, aber nicht funktioniert, da es echokein nimmt stdin, sondern nur sein Argument ausgibt. Hier ist das Argument $a, der Wert der Variablen ain der Unter-Shell, die ist 2.

  • Darüber hinaus a=10erzeugt keine Ausgabe, da es sich um eine Variablenzuweisung handelt. Es echo $awird also tatsächlich der Wert seines Arguments ausgegeben, der ist 2, und nichts von übernommen a=10 | < ... >. Daher sollte der Pipe-Operator hier nicht verwendet werden. Stattdessen können Sie den Befehlsnamen und die Variablenzuweisung mit einem Abschlusszeichen (, Semikolon) trennen, und es funktioniert einwandfrei, wie in (a=10; echo $a).

Um dies besser zu verstehen, können Sie die folgenden Beispiele mit aktivierter Bash-Debug-Option ausprobieren:

user@host$ a=1; a=2; a=10; echo $a;
  • In der obigen Befehlszeile echo $aerzeugt 10.

  • Wenn ich es ändere in

    user@host$ a=1; a=2; (a=10; echo $a)
    

    Die erste und zweite Variablenzuweisung wird in der aktuellen Shell festgelegt, die dritte Variablenzuweisung und echoder Befehl werden in einer Unter-Shell ausgeführt. Daher abefindet sich der Wert von 10in der Unter-Shell, in der echoauch der Befehl ausgeführt wird, und gibt daher zurück 10. Wenn Sie nach der Eingabeaufforderung den Befehl eingeben, echo $awird dieser zurückgegeben 2, da die Variablenzuweisungen aus der Unter-Shell nicht in die übergeordnete Shell übertragen werden.

  • Noch ein Hinweis: Der ;Befehlstrenner führt Befehle sequenziell aus.

In den Testfällen „A“ und „B“ wird die letzte Variablenzuweisung ( a=10in Test A und a=20in Test B) tatsächlich ausgeführt, aber sie wird nach dem echo $aBefehl ausgeführt, sodass Sie das Ergebnis des vorherigen Variablenwerts erhalten a, der sich in der Sub-Shell-Umgebung befindet, nach der die letzten Variablenzuweisungen ausgeführt werden. In einer Pipeline werden die stdinund stdoutzweier Befehle verbunden, bevor der Befehl ausgeführt wird, außerdem erzeugen Variablenzuweisungen nichts auf stdout.

tl;dr: Variablenzuweisung sollte nicht in einer Pipeline verwendet werden. echoFunktioniert nicht direkt in einer Pipeline.

Antwort3

Hier,

a=20 | echo $a

die Pipe sorgt nur für Verwirrung. Die Zuweisung auf der linken Seite druckt nichts auf stdout und echoliest nichts von stdin, sodass keine Daten durch die Pipe bewegt werden. Das Argument to echowird einfach von dem erweitert, was zuvor festgelegt wurde.

Wie Sie sagten, werden die Teile einer Pipeline in unterschiedlichen Unter-Shells ausgeführt, sodass die Zuweisung auf ader linken Seite keinen Einfluss auf die Erweiterung auf der rechten Seite hat und eine solche Kommunikation auch im umgekehrten Fall nicht stattfinden würde.

Wenn Sie stattdessen Folgendes getan haben:

{ a=999; echo a=$a; } | cat

Das Rohr würde mehr Sinn ergeben und die Schnur a=999würde hindurchgeführt werden.

Das touch fileA.txtim letzten Beispiel funktioniert, weil es das System außerhalb der Shell beeinflusst. Ebenso könnten Sie von den Befehlen in der Pipe aus in stderr schreiben und das Ergebnis auf dem Terminal erscheinen sehen:

$ echo a >&2 | echo b >&2 | echo c >&2
b
c
a

(Das ist tatsächlich die Ausgabereihenfolge, die ich mit Bash auf meinem System erhalten habe.)

verwandte Informationen