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=10
und echo $a
ausgefü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 a
ihren 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=10
nichts über die Pipe gesendet wird, kann es auch nicht beeinflussen echo $a
.
1 Wenn die lastpipe
Option gesetzt ist (sie ist standardmäßig deaktiviert und kann mit dem shopt
integrierten 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 $a
leiten Sie (mit dem Pipe-Operator ;|
)a=10
eine Verbindung zum Echo-Befehl weiter. Durch die Weiterleitung wird derstdout
Befehl a mitstdin
Befehl2 verbunden, d. hcommand | command2
. .a=10
ist eine Variablenzuweisung, ich gehe davon aus, dass esstdout
dafü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 $a
den Wert nicht zurück10
. Als ich es geändert habe zuuser@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 echo
nimmt 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 xargs
den 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 echo
Befehl, 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
a
wird1
zunächst der Wert zugewiesen, dann wird der Wert der Variablen in geändert2
, beides in der aktuellen Shell-Umgebung. Befehle werden im Allgemeinen in einer Unter-Shell ausgeführt.a=10 | echo $a
ist eine Liste, also gleichwertig zu ,(a=10 | echo $a)
das in einer Unter-Shell ausgeführt wird, aber nicht funktioniert, da esecho
kein nimmtstdin
, sondern nur sein Argument ausgibt. Hier ist das Argument$a
, der Wert der Variablena
in der Unter-Shell, die ist2
.Darüber hinaus
a=10
erzeugt keine Ausgabe, da es sich um eine Variablenzuweisung handelt. Esecho $a
wird also tatsächlich der Wert seines Arguments ausgegeben, der ist2
, und nichts von übernommena=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 $a
erzeugt10
.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
echo
der Befehl werden in einer Unter-Shell ausgeführt. Dahera
befindet sich der Wert von10
in der Unter-Shell, in derecho
auch der Befehl ausgeführt wird, und gibt daher zurück10
. Wenn Sie nach der Eingabeaufforderung den Befehl eingeben,echo $a
wird dieser zurückgegeben2
, 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=10
in Test A und a=20
in Test B) tatsächlich ausgeführt, aber sie wird nach dem echo $a
Befehl 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 stdin
und stdout
zweier 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. echo
Funktioniert 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 echo
liest nichts von stdin, sodass keine Daten durch die Pipe bewegt werden. Das Argument to echo
wird 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 a
der 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=999
würde hindurchgeführt werden.
Das touch fileA.txt
im 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.)