Warum verursacht die Verwendung von "Ja" für Bash-Pipelines * nicht * Endlosschleifen?

16

Der Dokumentation zufolge wartet die Bash, bis alle Befehle in einer Pipeline ausgeführt wurden, bevor sie fortgesetzt werden

Die Shell wartet, bis alle Befehle in der Pipeline beendet sind, bevor sie einen Wert zurückgibt.

Warum wird der Befehl dann yes | truesofort beendet? Sollte die yesSchleife nicht für immer andauern und die Pipeline niemals zurückkehren lassen?


Und eine Unterfrage: Gemäß der POSIX-Spezifikation können Shell-Pipelines entweder nach Beendigung des letzten Befehls zurückkehren oder warten, bis alle Befehle beendet sind. Haben gemeinsame Muscheln in diesem Sinne ein unterschiedliches Verhalten? Gibt es Muscheln, in denen yes | trueeine Endlosschleife erstellt wird?

hugomg
quelle
yes | tee >(true) >/dev/nullwird übrigens so teeweiter machen, wie Sie es erwarten, bis alle Autoren tot sind true.
Charles Duffy
1
trueist im Grunde ein {return 0;}Programm, also würde ich nicht erwarten, dass es lange läuft, geschweige denn für immer.
Dmitry Grigoryev

Antworten:

33

Beim trueVerlassen wird die Leseseite der Pipe geschlossen, es wird jedoch yesweiterhin versucht, auf die Schreibseite zu schreiben. Dieser Zustand wird als "broken pipe" bezeichnet und veranlasst den Kernel, ein SIGPIPESignal an zu senden yes. Da yesdieses Signal nichts Besonderes tut, wird es getötet. Wenn es das Signal ignoriert, schlägt sein writeAufruf mit Fehlercode fehl EPIPE. Programme, die dies tun, müssen darauf vorbereitet sein, dass sie es bemerken EPIPEund aufhören zu schreiben, sonst geraten sie in eine Endlosschleife.

Wenn Sie strace yes | true1 tun , können Sie sehen, wie sich der Kernel auf beide Möglichkeiten vorbereitet:

write(1, "y\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\n"..., 4096) = -1 EPIPE (Broken pipe)
--- SIGPIPE {si_signo=SIGPIPE, si_code=SI_USER, si_pid=17556, si_uid=1000} ---
+++ killed by SIGPIPE +++

straceüberwacht Ereignisse über die Debugger-API, die zuerst über den Systemaufruf informiert, der mit einem Fehler zurückkehrt, und dann über das Signal. Aus yesder Sicht von 's geschieht das Signal jedoch zuerst. (Technisch gesehen wird das Signal geliefert, nachdem der Kernel die Steuerung an den Benutzerbereich zurückgegeben hat, aber bevor weitere Maschinenanweisungen ausgeführt werden, sodass die write"Wrapper" -Funktion in der C-Bibliothek keine Möglichkeit erhält errno, die Anwendung festzulegen und zur Anwendung zurückzukehren.)


1 Leider straceist Linux-spezifisch. Die meisten modernen Unixe haben einen Befehl, der etwas Ähnliches tut, aber oft einen anderen Namen hat, Syscall-Argumente wahrscheinlich nicht so gründlich dekodiert und manchmal nur für root funktioniert.

casey
quelle
3
@hugomg in diesem Fall ist die Pipe völlig irrelevant.
muru
3
@hugomg, weil nichts in yesder Pipe verbunden ist.
Muru
4
Es ist wahr, das ist eine Demonstration des dokumentierten Verhaltens "Warten Sie, bis alle Befehle beendet sind, bevor Sie die Pipeline beenden". Es wird lediglich verhindert, yesdass SIGPIPE abgerufen wird, da der FD, auf den geschrieben wird, nicht mit einer Pipe verbunden ist.
Tom Hunt
2
@hugomg, es wiederholt sich für immer auf dieselbe Weise wie yes >/dev/nullfür immer. Es zeigt überhaupt nichts über Pipelines, was nicht auch für einfache Befehle gilt (wie das Verhalten beim Warten auf Beendigung, das Tom hervorhebt, gilt auch für einfache Befehle).
Charles Duffy
2
@zwol: Ich denke, wir verwenden unsere Begriffe hier mit leicht unterschiedlichen Bedeutungen oder denken über Dinge aus leicht unterschiedlichen Perspektiven nach ... aber in beiden Fällen write()kehrt die Funktion in libc erst nachher zurück (Übertragung der Kontrolle auf den darauf folgenden PC) Der Signalhandler wurde ausgeführt, aber da der Signalhandler das Programm beendet, wird die Steuerung niemals übertragen und write()kehrt daher niemals zurück. Ja, das wird im Kernel implementiert, indem eine xxx_write()Funktion zurückgegeben wird -EPIPE, aber wir debuggen ein User-Space-Programm und sind daran nicht interessiert.
Dietrich Epp
5

Gibt es Muscheln, bei denen ja | true wird für immer loopen?

Unwahrscheinlich, da der yesBefehl die Pipe verwendet und sie fehlschlägt, wenn die Pipe unterbrochen ist. sleepbenutzt die Pipe dagegen nicht, also:

sleep 100000000 | true

läuft mindestens 100000000 Sekunden lang.

muru
quelle
2
Seien Sie vorsichtig bei allen modernen Shells, die nicht für den letzten (am weitesten rechts stehenden) Befehl in einer Pipe bestimmt sind und wo truesich ein eingebauter befindet. Dies gilt für die neueren Versionen von der Bourne Shell, ksh93, zsh. Wenn Sie ^Zbei Ausführung eines solchen Befehls drücken, wird der Energiesparmodus unterbrochen, und die Shell kann ohne externe Hilfe keine Wiederherstellung durchführen.
Schily
3
zsh 4.3.4 (i386-pc-solaris2.11) hier, so scheint es, dass dies vor kurzem geändert wurde. Interessante Idee, ich muss mal schauen, ob ich einen ähnlichen Fix für die Bourne Shell implementieren kann. Es bleibt die Frage, wie es funktioniert und welche tty-Prozessgruppe wie in der Bourne-Shell verwendet wird. Die Tatsache, dass der oberste Befehl eine eingebaute ist, wird entdeckt, nachdem die Prozessgruppe für den Ruhezustand bereits für immer eingerichtet wurde.
Schily
2
@CharlesDuffy, so wie ich es verstehe, pflegt schily eine Version von sh, auf die er Verbesserungen aus modernen Shells zurückportiert. Er hat hier irgendwo darüber geschrieben.
muru
3
Die Bourne-Shell im Erbstück-Archiv wurde bis ~ 2007 beibehalten, jedoch nie vollständig portierbar gemacht, da sie noch Anrufe an enthält sbrk(). Eine portable und gepflegte Version ist im schily tools Bundle enthalten und @Charles Duffy hat bereits einen Ort für Informationen gefunden ;-)
schily
2
@muru Viele der Funktionen, die ich auf die Bourne-Shell bshzurückportiert habe, stammen von mir (Berthold Shell von VBERTOS, einer mit virtuellem Speicher ausgestatteten Version von UNOS - dem ersten UNIX-Klon). Bsh erhielt 1984 und 1985 viele csh-Funktionen, aber der Alias-Mechanismus von UNOS war bereits 1980 dem von csh überlegen. Andere neue Bourne Shell-Funktionen stammen von POSIX, damit POSIX-Konformität erreicht wird.
Schily