Das
echo one; echo two > >(cat); echo three;
Befehl gibt eine unerwartete Ausgabe aus.
Ich habe folgendes gelesen: Wie wird die Prozessersetzung in bash implementiert? und viele andere Artikel zum Thema Prozesssubstitution im Internet, verstehen aber nicht, warum es sich so verhält.
Erwartete Ausgabe:
one
two
three
Wirkliche Leistung:
prompt$ echo one; echo two > >(cat); echo three;
one
three
prompt$ two
Auch diese beiden Befehle sollten aus meiner Sicht gleichwertig sein, aber sie tun es 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 denke ich, sollten sie gleich sein? Denn beides verbindet die seq
Ausgabe mit der cat
Eingabe über die anonyme Pipe - Wikipedia, Prozessersetzung .
Frage: Warum verhält es sich so? Wo ist mein Fehler? Die umfassende Antwort ist erwünscht (mit Erläuterung, wie das bash
geht, unter der Haube).
bash
process-substitution
MiniMax
quelle
quelle
Antworten:
Ja, in
bash
like inksh
(woher die Funktion stammt) wird nicht auf die Prozesse innerhalb der Prozessersetzung gewartet (bevor der nächste Befehl im Skript ausgeführt wird).Für
<(...)
einen ist das normalerweise in Ordnung wie in:Die Shell wartet darauf
cmd1
undcmd1
wartet normalerweise darauf,cmd2
bis das Dateiende in der ersetzten Pipe abgelesen wird. Das Dateiende tritt normalerweise auf, wenn die Pipecmd2
stirbt. Das ist der gleiche Grund , mehrere Schalen (nichtbash
) nicht warten , störencmd2
incmd2 | cmd1
.Dies
cmd1 >(cmd2)
ist jedoch in der Regel nicht der Fall, da in der Regel mehrcmd2
darauf gewartetcmd1
wird, bis das Programm beendet ist.Das ist darin behoben,
zsh
dasscmd2
dort gewartet wird (aber nicht, wenn Sie es als schreibencmd1 > >(cmd2)
undcmd1
es nicht eingebaut ist, verwenden Sie es{cmd1} > >(cmd2)
stattdessen wie dokumentiert ).ksh
wait
Wartet nicht standardmäßig, sondern lässt dich mit dem eingebauten Programm darauf warten (es stellt auch die PID in zur Verfügung$!
, obwohl das nicht hilft, wenn du das tustcmd1 >(cmd2) >(cmd3)
)rc
(mit dercmd1 >{cmd2}
Syntax), mit derksh
Ausnahme, dass Sie die Pids aller Hintergrundprozesse mit erhalten können$apids
.es
(auch mitcmd1 >{cmd2}
) wartet aufcmd2
like inzsh
und wartet auch aufcmd2
in<{cmd2}
Bearbeitung befindliche Weiterleitungen.bash
cmd2
Stellt die PID von (oder genauer gesagt von der Subshell, da siecmd2
in einem untergeordneten Prozess dieser Subshell ausgeführt wird, obwohl dies der letzte Befehl dort ist) in zur Verfügung$!
, lässt Sie jedoch nicht darauf warten.Wenn Sie verwenden müssen
bash
, können Sie das Problem umgehen, indem Sie einen Befehl verwenden, der auf beide Befehle wartet:Das macht beides
cmd1
undcmd2
hat ihre fd 3 zu einer Pfeife geöffnet.cat
Wartet auf das Dateiende am anderen Ende, wird also normalerweise nur beendet, wenn beide beendet sindcmd1
undcmd2
tot sind. Und die Shell wartet auf diesencat
Befehl. Sie können dies als ein Netz betrachten, um die Beendigung aller Hintergrundprozesse&
abzufangen (Sie können es für andere Dinge verwenden, die im Hintergrund gestartet wurden , z. B. für Coprocs oder sogar für Befehle, die selbst im Hintergrund ausgeführt werden, vorausgesetzt, sie schließen nicht alle ihre Dateideskriptoren, wie es Daemons normalerweise tun ).Beachten Sie, dass es dank des oben erwähnten verschwendeten Subshell-Prozesses auch dann funktioniert, wenn
cmd2
fd 3 geschlossen wird (Befehle tun dies normalerweise nicht, aber einige mögensudo
oderssh
tun dies). Zukünftige Versionen vonbash
können eventuell die Optimierung dort wie in anderen Shells durchführen. Dann brauchen Sie etwas wie:Um sicherzustellen, dass es noch einen zusätzlichen Shell-Prozess gibt, bei dem fd 3 geöffnet ist und auf diesen
sudo
Befehl wartet .Beachten Sie, dass
cat
nichts gelesen wird (da die Prozesse nicht auf ihre fd 3 schreiben). Es ist nur für die Synchronisation da. Es wird nur einread()
Systemaufruf ausgeführt, der am Ende nichts zurückgibt.Sie können das Ausführen tatsächlich vermeiden,
cat
indem Sie eine Befehlsersetzung verwenden, um die Pipesynchronisierung durchzuführen:Diesmal ist es die Shell
cat
, die aus der Pipe liest, deren anderes Ende auf fd 3 voncmd1
und offen istcmd2
. Wir verwenden eine variable Zuweisung, damit der Exit-Status voncmd1
in verfügbar ist$?
.Oder Sie können die Prozessersetzung von Hand durchführen und dann sogar die Ihres Systems verwenden,
sh
da dies zur Standard-Shell-Syntax werden würde:obwohl zur Kenntnis , wie bereits festgestellt , dass nicht alle
sh
Implementierungen für warten würden ,cmd1
nachdemcmd2
beendet (obwohl das besser ist , als umgekehrt). Diese Zeit$?
enthält den Exit-Status voncmd2
; obwohlbash
undzsh
makecmd1
‚s Exit - Status in${PIPESTATUS[0]}
und$pipestatus[1]
jeweils (siehe auch diepipefail
Option in einigen Schalen so$?
kann berichten , das Versagen von Rohrkomponenten außer den letzten)Beachten Sie, dass
yash
ähnliche Probleme mit seinem Prozess hat Umleitung Funktion.cmd1 >(cmd2)
würde dort geschriebencmd1 /dev/fd/3 3>(cmd2)
werden. Aber escmd2
wird nicht darauf gewartet, und Sie können auch nichtwait
darauf warten, und die pid wird auch nicht in der$!
Variablen verfügbar gemacht . Sie würden die gleichen Workarounds wie für verwendenbash
.quelle
echo one; { { echo two > >(cat); } 3>&1 >&4 4>&- | cat; } 4>&1; echo three;
, dann auf vereinfachtecho one; echo two > >(cat) | cat; echo three;
und es gibt auch Werte in der richtigen Reihenfolge aus. Sind all diese Deskriptormanipulationen3>&1 >&4 4>&-
notwendig? Außerdem verstehe ich das nicht>&4 4>&
- wir werdenstdout
zum vierten fd umgeleitet , schließen dann den vierten fd und verwenden4>&1
ihn dann erneut . Warum brauchte es und wie funktioniert es? Vielleicht sollte ich eine neue Frage zu diesem Thema erstellen?cmd1
undcmd2
. Der springende Punkt beim kleinen Tanz mit dem Dateideskriptor ist, die ursprünglichen wiederherzustellen und nur die zusätzliche Pipe für das Warten zu verwenden, anstatt auch die Ausgabe der Befehle zu kanalisieren.4>&1
wird ein Dateideskriptor (fd) 4 für die Befehlsliste der äußeren geschweiften Klammern erstellt, der dem Standardwert der äußeren geschweiften Klammern entspricht. Die inneren Zahnspangen haben stdin / stdout / stderr automatisch eingerichtet, um eine Verbindung zu den äußeren Zahnspangen herzustellen.3>&1
Führt jedoch dazu, dass fd 3 mit dem Standard der äußeren Klammern verbunden wird.>&4
Lässt das stdout der inneren Zahnspange mit dem fd 4 der äußeren Zahnspange verbunden werden (der zuvor erstellte).4>&-
schließt fd 4 von den inneren Klammern ab (Da stdout der inneren Klammern bereits mit fd 4 der äußeren Klammern verbunden ist).4>&1
der zuerst ausgeführt wird, bevor die anderen Umleitungen ausgeführt werden, damit Sie ihn nicht "erneut verwenden4>&1
". Insgesamt sendet die innere Klammer Daten an ihre Standardausgabe, die mit der angegebenen FD 4 überschrieben wurde. Das fd 4, das der inneren Zahnspange gegeben wurde, ist das fd 4 der äußeren Zahnspange, das dem ursprünglichen stdout der äußeren Zahnspange entspricht.4>5
, als würde "4 geht zu 5" bedeuten, aber wirklich "fd 4 wird mit fd 5 überschrieben". Und vor der Ausführung werden fd 0/1/2 automatisch verbunden (zusammen mit jedem fd der äußeren Hülle), und Sie können sie überschreiben, wie Sie möchten. Das ist zumindest meine Interpretation der Bash-Dokumentation. Wenn Sie verstehen etwas anderes aus diesem , lmk.Sie können den zweiten Befehl an einen anderen
cat
weiterleiten. Dieser wartet, bis die Eingabe-Pipe geschlossen wird. Ex:Kurz und einfach.
==========
So einfach es auch scheint, hinter den Kulissen spielt sich viel ab. Sie können den Rest der Antwort ignorieren, wenn Sie nicht daran interessiert sind, wie dies funktioniert.
Wenn Sie haben
echo two > >(cat); echo three
,>(cat)
wird durch die interaktive Shell gegabelt und läuft unabhängig vonecho two
. Somitecho two
endet und wird dannecho three
ausgeführt, aber bevor das>(cat)
Ziel erreicht ist. Wennbash
Daten>(cat)
abgerufen werden, von denen sie nicht erwartet wurden (ein paar Millisekunden später), erhalten Sie die sofortige Situation, in der Sie die Newline-Taste drücken müssen, um zum Terminal zurückzukehren (so, als ob ein anderer Benutzermesg
Sie bearbeitet hätte ).Es werden jedoch
echo two > >(cat) | cat; echo three
zwei Subshells erzeugt (gemäß der Dokumentation des|
Symbols).Eine Unterschale mit dem Namen A ist für
echo two > >(cat)
und eine Unterschale mit dem Namen B ist fürcat
. A wird automatisch mit B verbunden (A's Standard ist B's Standard). Dannecho two
und>(cat)
beginnen Sie mit der Ausführung.>(cat)
's stdout ist auf A's stdout gesetzt, was B's stdin entspricht. Nach demecho two
Beenden wird A beendet und die Standardausgabe geschlossen. Hält>(cat)
jedoch immer noch den Verweis auf B's stdin. Dascat
stdin der Sekunde hält das stdin von B und dascat
wird nicht beendet, bis es ein EOF sieht. Ein EOF wird nur dann gegeben, wenn niemand mehr die Datei im Schreibmodus geöffnet hat, also>(cat)
blockiert stdout die zweitecat
. B bleibt auf diese Sekunde wartencat
. Daecho two
wird>(cat)
irgendwann ein EOF abgegeben, also>(cat)
Leert den Puffer und beendet das Programm. Niemand hältcat
mehr die Standardanzeige von B / Sekunde , dahercat
liest die zweite eine EOF (B liest die Standardanzeige überhaupt nicht, es ist ihm egal). Dieser EOF bewirkt, dass der zweitecat
seinen Puffer leert, seine Standardausgabe schließt und beendet, und dann wird B beendet, weil er beendet wurdecat
und B darauf gewartet hatcat
.Eine Einschränkung davon ist, dass Bash auch eine Subshell für erzeugt
>(cat)
! Aus diesem Grund werden Sie das sehenecho two > >(sleep 5) | cat; echo three
Wartet immer noch 5 Sekunden vor der Ausführung
echo three
, auch wennsleep 5
B's Standard nicht hält. Dies liegt daran, dass eine verborgene Subshell, auf die C gespawnt hat,>(sleep 5)
wartetsleep
und C Bs Standard enthält. Sie können sehen, wieecho two > >(exec sleep 5) | cat; echo three
sleep
Wartet jedoch nicht, da B nicht die Standardeinstellung enthält und es keine Geister-Subshell C gibt, die die Standardeinstellung von B enthält (exec erzwingt den Ruhezustand, um C zu ersetzen, anstatt C zu forken und darauf warten zu lassensleep
). Unabhängig von dieser Einschränkung,echo two > >(exec cat) | cat; echo three
führt die Funktionen weiterhin ordnungsgemäß in der zuvor beschriebenen Reihenfolge aus.
quelle
A
wartet nicht auf dencat
eingebrachten>(cat)
. Wie ich in meiner Antwort erwähnt, der Grund , warumecho two > >(sleep 5 &>/dev/null) | cat; echo three
gibtthree
nach 5 Sekunden, da aktuelle Versionenbash
einen zusätzlichen Shell - Prozesses anfallende Abfallmenge,>(sleep 5)
dass darauf wartet ,sleep
und dieser Prozess ist immer noch stdout zu dem ,pipe
welche den zweiten verhindertcat
von Abschluss. Wenn Sie es durch ersetzenecho two > >(exec sleep 5 &>/dev/null) | cat; echo three
, um diesen zusätzlichen Prozess zu eliminieren, werden Sie feststellen, dass es sofort zurückkehrt.echo two > >(sleep 5 &>/dev/null)
das Minimum eine eigene Subshell bekommt. Ist es ein nicht dokumentiertes Implementierungsdetail, das dazu führtsleep 5
, dass auch eine eigene Subshell erstellt wird? Wenn es dokumentiert ist, wäre es eine legitime Möglichkeit, es mit weniger Zeichen zu erledigen (es sei denn, es gibt eine enge Schleife, ich glaube nicht, dass jemand Leistungsprobleme mit einer Subshell oder einer Katze bemerkt). Wenn es nicht dokumentiert ist, funktioniert Rippen, netter Hack, in zukünftigen Versionen jedoch nicht.$(...)
,<(...)
Sie beinhalten in der Tat eine Subshell, aber ksh93 oder zsh würde den letzten Befehl in dieser Subshell im gleichen Prozess, nicht ausgeführtbash
, weshalb es gibt noch einen anderen Prozess das Rohr geöffnet und haltensleep
läuft ein das Rohr nicht offen halten. Zukünftige Versionen vonbash
können eine ähnliche Optimierung implementieren.exec
Bedingungen wie erwartet funktioniert.