Die
echo one; echo two > >(cat); echo three;
Befehl gibt unerwartete Ausgabe.
Ich habe das gelesen:Wie wird die Prozesssubstitution in Bash implementiert? und viele andere Artikel über Prozesssubstitution im Internet, verstehe aber nicht, warum es sich so verhält.
Erwartete Ausgabe:
one
two
three
Echter Output:
prompt$ echo one; echo two > >(cat); echo three;
one
three
prompt$ two
Auch diese beiden Befehle sollten aus meiner Sicht gleichwertig sein, tun es aber nicht:
##### first command - the pipe is used.
prompt$ seq 1 5 | cat
1
2
3
4
5
##### second command - the process substitution and redirection are used.
prompt$ seq 1 5 > >(cat)
prompt$ 1
2
3
4
5
Warum ich denke, sie sollten gleich sein? Denn beides verbindet die seq
Ausgabe an cat
Eingabe durch die anonyme Pipe – Wikipedia, Process Substitution.
Frage: Warum verhält es sich so? Wo ist mein Fehler? Die umfassende Antwort ist erwünscht (mit Erläuterung, wie die bash
tut es unter der Haube).
Akzeptierte Antwort:
Ja, in bash
wie in ksh
(woher das Feature kommt), wird nicht auf die Prozesse innerhalb der Prozesssubstitution gewartet (bevor der nächste Befehl im Skript ausgeführt wird).
für einen <(...)
Eins, das ist normalerweise in Ordnung wie in:
cmd1 <(cmd2)
die Shell wartet auf cmd1
und cmd1
wartet normalerweise auf cmd2
aufgrund dessen, dass bis zum Dateiende auf der ersetzten Pipe gelesen wird, und dieses Dateiende tritt normalerweise auf, wenn cmd2
stirbt. Das ist der gleiche Grund, warum mehrere Shells (nicht bash
) machen Sie sich nicht die Mühe, auf cmd2
zu warten in cmd2 | cmd1
.
Für cmd1>(cmd2)
Dies ist jedoch im Allgemeinen nicht der Fall, da es sich eher um cmd2
handelt die typischerweise auf cmd1
wartet dort wird es im Allgemeinen danach beendet.
Das ist in zsh
behoben die auf cmd2
wartet dort (aber nicht, wenn Sie es als cmd1>>(cmd2)
schreiben und cmd1
nicht eingebaut ist, verwenden Sie {cmd1}>>(cmd2)
stattdessen wie dokumentiert).
ksh
wartet standardmäßig nicht, sondern lässt Sie mit wait
darauf warten builtin (es macht auch die PID in $!
verfügbar , obwohl das nicht hilft, wenn Sie cmd1>(cmd2)>(cmd3)
ausführen )
rc
(mit dem cmd1>{cmd2}
Syntax), dasselbe wie ksh
außer dass Sie die PIDs aller Hintergrundprozesse mit $apids
erhalten können .
es
(auch mit cmd1>{cmd2}
) wartet auf cmd2
wie in zsh
, und wartet auch auf cmd2
in <{cmd2}
Weiterleitungen verarbeiten.
bash
macht die PID von cmd2
(oder genauer gesagt der Subshell, da sie cmd2
ausführt in einem untergeordneten Prozess dieser Subshell, obwohl es der letzte Befehl dort ist), der in $!
verfügbar ist , lässt Sie aber nicht darauf warten.
Wenn Sie bash
verwenden müssen , können Sie das Problem umgehen, indem Sie einen Befehl verwenden, der auf beide Befehle wartet, mit:
{ { cmd1 >(cmd2); } 3>&1 >&4 4>&- | cat; } 4>&1
Das macht sowohl cmd1
und cmd2
haben ihre fd 3 offen zu einem Rohr. Katze
wartet auf das Dateiende am anderen Ende, wird also normalerweise nur beendet, wenn beide cmd1
und cmd2
sind tot. Und die Shell wird auf diese cat
warten Befehl. Sie könnten das als ein Netz sehen, um die Beendigung aller Hintergrundprozesse abzufangen (Sie können es für andere Dinge verwenden, die im Hintergrund gestartet werden, wie mit &
, Coprocs oder sogar Befehle, die sich selbst im Hintergrund befinden, vorausgesetzt, sie schließen nicht alle ihre Dateideskriptoren, wie es normalerweise Daemons tun).
Beachten Sie, dass es dank des oben erwähnten verschwendeten Subshell-Prozesses auch dann funktioniert, wenn cmd2
schließt seine fd 3 (Befehle tun das normalerweise nicht, aber einige wie sudo
oder ssh
tun). Zukünftige Versionen von bash
möglicherweise die Optimierung dort wie in anderen Shells durchführen. Dann bräuchten Sie etwas wie:
{ { cmd1 >(sudo cmd2; exit); } 3>&1 >&4 4>&- | cat; } 4>&1
Um sicherzustellen, dass noch ein zusätzlicher Shell-Prozess mit diesem geöffneten fd 3 auf dieses sudo
wartet Befehl.
Beachten Sie, dass cat
wird nichts lesen (da die Prozesse nicht auf ihren fd 3 schreiben). Es dient lediglich der Synchronisation. Es wird nur ein read()
ausgeführt Systemaufruf, der am Ende nichts zurückgibt.
Sie können tatsächlich vermeiden, cat
auszuführen indem Sie eine Befehlssubstitution verwenden, um die Pipe-Synchronisierung durchzuführen:
{ unused=$( { cmd1 >(cmd2); } 3>&1 >&4 4>&-); } 4>&1
Diesmal ist es die Shell statt cat
das liest aus der Pipe, deren anderes Ende auf fd 3 von cmd1
offen ist und cmd2
. Wir verwenden eine Variablenzuweisung, also den Exit-Status von cmd1
ist in $?
verfügbar .
Oder Sie könnten die Prozessersetzung von Hand durchführen und dann sogar das sh
Ihres Systems verwenden da dies zur Standard-Shell-Syntax werden würde:
{ cmd1 /dev/fd/3 3>&1 >&4 4>&- | cmd2 4>&-; } 4>&1
Beachten Sie jedoch, wie bereits erwähnt, dass nicht alle sh
Implementierungen würden auf cmd1
warten nach cmd2
fertig ist (obwohl das besser ist als umgekehrt). Dieses Mal $?
enthält den Exit-Status von cmd2
; obwohl bash
und zsh
machen Sie cmd1
Der Exit-Status ist in ${PIPESTATUS[0]}
verfügbar und $pipestatus[1][code> (siehe auch
pipefail
Option in einigen Shells, also $?
kann den Ausfall anderer Rohrkomponenten als der letzten melden)
Beachten Sie, dass yash
hat ähnliche Probleme mit seinem Prozess Umleitung Merkmal. cmd1>(cmd2)
würde geschrieben werden cmd1 /dev/fd/3 3>(cmd2)
dort. Aber cmd2
wird nicht gewartet und Sie können wait
nicht verwenden auch darauf zu warten und seine PID wird nicht im $!
zur Verfügung gestellt auch variabel. Sie würden die gleichen Problemumgehungen wie für bash
verwenden .