Warum erhalte ich $x
aus den folgenden Ausschnitten andere Werte ?
#!/bin/bash
x=1
echo fred > junk ; while read var ; do x=55 ; done < junk
echo x=$x
# x=55 .. I'd expect this result
x=1
cat junk | while read var ; do x=55 ; done
echo x=$x
# x=1 .. but why?
x=1
echo fred | while read var ; do x=55 ; done
echo x=$x
# x=1 .. but why?
Antworten:
Die richtige Erklärung wurde bereits von jsbillings und geekosaur gegeben , aber lassen Sie mich das etwas näher erläutern .
In den meisten Shells, einschließlich Bash, wird jede Seite einer Pipeline in einer Subshell ausgeführt, sodass jede Änderung des internen Status der Shell (z. B. das Festlegen von Variablen) auf dieses Segment einer Pipeline beschränkt bleibt. Die einzige Information, die Sie von einer Subshell erhalten können, ist die Ausgabe (für die Standardausgabe und andere Dateideskriptoren) und der Exit-Code (eine Zahl zwischen 0 und 255). Das folgende Snippet gibt beispielsweise 0 aus:
In ksh (die vom AT & T-Code abgeleiteten Varianten, nicht pdksh / mksh-Varianten) und zsh wird das letzte Element in einer Pipeline in der übergeordneten Shell ausgeführt. (POSIX erlaubt beide Verhaltensweisen.) Das obige Snippet gibt also 2 aus.
Eine nützliche Redewendung ist, die Fortsetzung der while-Schleife (oder was auch immer Sie auf der rechten Seite der Pipeline haben, aber eine while-Schleife ist hier tatsächlich üblich) in die Pipeline aufzunehmen:
quelle
< <(locate -ber ^\.tag$)
, dank der ursprünglichen, leicht unklaren Antwort und der Kommentare von Geekosaurier und Glenn Jackman. Ich war anfangs in einem Dilemma, die Antwort zu akzeptieren, aber das Nettoergebnis war ziemlich klar, vor allem mit jsbillings Follow-up-Kommentar :)Sie stoßen auf ein Problem mit dem variablen Bereich. Die in der while-Schleife auf der rechten Seite der Pipe definierten Variablen haben einen eigenen lokalen Gültigkeitsbereichskontext. Änderungen an der Variablen werden außerhalb der Schleife nicht angezeigt. Die while-Schleife ist im Wesentlichen eine Subshell, die ein COPY der Shell-Umgebung abruft , und alle Änderungen an der Umgebung gehen am Ende der Shell verloren. Siehe diese StackOverflow-Frage .
AKTUALISIERT : Ich habe versäumt, auf die wichtige Tatsache hinzuweisen, dass die while-Schleife mit ihrer eigenen Subshell der Endpunkt einer Pipe ist. Ich habe dies in der Antwort aktualisiert.
quelle
while
Schleife wird als hinteres Ende einer Pipeline ausgeführt, die sie in eine Subshell wirft.blah|blah|while read ...
können Sie habenwhile read ...; done < <(blah|blah)
Wie in anderen Antworten erwähnt , verlaufen die Teile einer Pipeline in Unterschalen, sodass dort vorgenommene Änderungen für die Hauptschale nicht sichtbar sind.
Wenn wir nur Bash betrachten, gibt es neben der
cmd | { stuff; more stuff; }
Struktur noch zwei weitere Problemumgehungen :Leiten Sie die Eingabe von der Prozessersetzung um :
Die Ausgabe des Befehls in
<(...)
sieht aus, als wäre es eine Named Pipe.Die
lastpipe
Option, mit der Bash wie ksh funktioniert und den letzten Teil der Pipeline im Hauptshellprozess ausführt. Dies funktioniert jedoch nur, wenn die Auftragssteuerung deaktiviert ist, dh nicht in einer interaktiven Shell:oder
Die Prozessersetzung wird natürlich auch in ksh und zsh unterstützt. Da der letzte Teil der Pipeline ohnehin in der Hauptshell ausgeführt wird, ist es nicht unbedingt erforderlich, ihn als Workaround zu verwenden.
quelle
es kann funktionieren.
quelle