Pipes, wie fließen Daten in einer Pipeline?

22

Ich verstehe nicht, wie die Daten in der Pipeline fließen und hoffe, jemand könnte klären, was dort vor sich geht.

Ich dachte, eine Pipeline von Befehlen verarbeitet Dateien (Text, Arrays von Strings) zeilenweise. (Wenn jeder Befehl Zeile für Zeile ausgeführt wird.) Jede Textzeile durchläuft die Pipeline. Befehle warten nicht auf die vorherige, um die Verarbeitung der gesamten Eingabe abzuschließen.

Es scheint aber nicht so zu sein.

Hier ist ein Testbeispiel. Es gibt einige Textzeilen. Ich schreibe sie in Großbuchstaben und wiederhole jede Zeile zweimal. Ich mache das mit cat text | tr '[:lower:]' '[:upper:]' | sed 'p'.

Um den Vorgang zu verfolgen, können wir ihn "interaktiv" ausführen - überspringen Sie den eingegebenen Dateinamen cat. Jeder Teil der Pipeline verläuft zeilenweise:

$ cat | tr '[:lower:]' '[:upper:]'
alkjsd
ALKJSD
sdkj
SDKJ
$ cat | sed 'p'
line1
line1
line1
line 2
line 2
line 2

Die gesamte Pipeline wartet jedoch darauf, dass ich die Eingabe mit beende EOFund gibt erst dann das Ergebnis aus:

$ cat | tr '[:lower:]' '[:upper:]' | sed 'p'
I am writing...
keep writing...
now ctrl-D
I AM WRITING...
I AM WRITING...
KEEP WRITING...
KEEP WRITING...
NOW CTRL-D
NOW CTRL-D

Soll es so sein Warum ist es nicht Zeile für Zeile?

Xealits
quelle
Es ist nicht die Pipe, es catpuffert, bis stdin sich schließt.
Goldlöckchen
aber trund sedverarbeite Zeilen catbevor stdin schließt
xealits
Die von stdio verwendeten Standardeinstellungen (von denen ich glaube, dass alle genannten Programme sie verwenden) sind, dass stderr ungepuffert ist und stdout beim Schreiben in ein Terminal zeilengepuffert und ansonsten vollständig gepuffert ist (zum Beispiel beim Schreiben in eine Datei oder eine Pipe). . Einige der Befehle haben Flags, die die stdout-Pufferung ändern können, aber es sieht so aus, als ob tr dies nicht tut.
Kasperd

Antworten:

36

Es gibt eine allgemeine Pufferregel, die von der C-Standard-E / A-Bibliothek ( stdio) befolgt wird, die die meisten Unix-Programme verwenden. Wenn die Ausgabe an ein Terminal gesendet wird, wird sie am Ende jeder Zeile gelöscht. Andernfalls wird es nur geleert, wenn der Puffer (8 KB auf meinem Linux / amd64-System; könnte bei Ihnen anders sein) voll ist.

Wenn alle Dienstprogramme , die allgemeine Regel folgende, würden Sie die Ausgabe in alle Ihre Beispiele sehen verzögert ( cat|sed, cat|trund cat|tr|sed). Aber es gibt eine Ausnahme: GNU catpuffert niemals seine Ausgabe. Entweder wird stdiodie Standardpufferrichtlinie nicht verwendet oder geändert stdio.

Ich kann ziemlich sicher sein, dass Sie GNU catund kein anderes Unix verwenden, catda sich die anderen nicht so verhalten würden. Herkömmliches Unix catbietet die -uMöglichkeit, ungepufferte Ausgaben anzufordern. GNU catignoriert die -uOption, da die Ausgabe immer ungepuffert ist.

Wenn Sie also eine Pipe mit einem catlinks im GNU-System haben, wird der Durchgang von Daten durch die Pipe nicht verzögert. Das catgeht nicht mal Zeile für Zeile - das macht Ihr Terminal. Während Sie Eingaben für cat eingeben, befindet sich Ihr Terminal im "kanonischen" Modus - zeilenbasiert. Mit Bearbeitungstasten wie Rücktaste und Strg-U haben Sie die Möglichkeit, die eingegebene Zeile vor dem Senden zu bearbeiten Enter.

In diesem cat|tr|sedBeispiel werden trweiterhin Daten von empfangen, catsobald Sie auf drücken Enter, es trwird jedoch die stdioStandardrichtlinie befolgt: Die Ausgabe wird an eine Pipe gesendet, sodass nicht nach jeder Zeile eine Leerung erfolgt. Es schreibt in die zweite Pipe, wenn der Puffer voll ist oder wenn eine EOF empfangen wird, je nachdem, was zuerst eintritt.

sedfolgt ebenfalls der stdioStandardrichtlinie, aber die Ausgabe wird an ein Terminal gesendet, sodass jede Zeile geschrieben wird, sobald sie fertig ist. Dies hat Auswirkungen darauf, wie viel Sie eingeben müssen, bevor etwas am anderen Ende der Pipeline angezeigt wird. Wenn sedSie die Ausgabe blockpuffern, müssen Sie doppelt so viel eingeben (um trden Ausgabepuffer und sed die Ausgabe zu füllen) Puffer).

GNU sedhat die -uOption, dass, wenn Sie die Reihenfolge umkehren und verwenden cat|sed -u|tr, die Ausgabe sofort wieder angezeigt wird. (Die sed -uOption könnte an anderer Stelle verfügbar sein, aber ich glaube nicht, dass es sich um eine alte Unix-Tradition handelt. cat -u) Soweit ich das beurteilen kann, gibt es für keine entsprechende Option tr.

Es gibt ein Hilfsprogramm, mit stdbufdem Sie den Puffermodus jedes Befehls ändern können, der die stdioStandardeinstellungen verwendet. Es ist ein bisschen zerbrechlich, da es verwendet wird LD_PRELOAD, um etwas zu erreichen, für das die C-Bibliothek nicht entwickelt wurde, aber in diesem Fall scheint es zu funktionieren:

cat | stdbuf -o 0 tr '[:lower:]' '[:upper:]' | sed 'p'

quelle
1
Vielen Dank! Geniale Antwort. Wahrscheinlich sollte ich die Pufferung in der Frage irgendwie erwähnen, damit man sie finden kann.
Xealits
teeund ddnormalerweise auch nach ihren eigenen Regeln spielen. Wenn diese drei Werkzeuge phantasievoll kombiniert werden, können sie praktisch jede Notwendigkeit für stdbufPipelines im Hintergrund zunichte machen.
mikeserv
1
Dies ist einer der Gründe, um einen unnötigen Einsatz von Katze zu vermeiden .
Hobbs
8

Das hat mich tatsächlich einige Gedanken gekostet, um zu verstehen und noch mehr, um zu antworten. Tolle Frage (ich werde es als nächstes bewerten).

Sie haben es versäumt, tr | sedIhre obigen Debugging-Elemente einzugeben:

>tr '[:lower:]' '[:upper:]' | sed 'p'
i am writing
still writing
now ctrl-d
I AM WRITING
I AM WRITING
STILL WRITING
STILL WRITING
NOW CTRL-D
NOW CTRL-D
>

Also offenbar trPuffer. Lerne jeden Tag etwas Neues!

EDIT :

Während ich darüber nachdenke, haben wir die Ursache isoliert, aber keine Erklärung geliefert. Wenn Sie cat | tr, schreibt es sofort, wenn Sie cat | sed, es sofort schreibt, aber wenn Sie tr | sed, es wartet für EOF. Ich würde vorschlagen, dass die Antwort in troder im sedQuellcode vergraben ist und kein Pipe-Problem darstellt.

EDIT :

Ich sehe, dass Wumpus die Erklärung geliefert hat, während ich die letzte Änderung getippt habe. Vielen Dank!

Poisson Aerokopf
quelle
1
in der Tat puffern sie! und der Test mit ungefähr 8 kb Leitungen, wie Wumpus erwähnte, zeigt, dass der Puffer tatsächlich 8 kb ist. Ich würde gerne beide Antworten akzeptieren, um einen guten Ruf zu teilen, aber ich werde Wumpus 'Antwort als vollständigere ansehen. Danke trotzdem!
Xealits
1
Kein Problem, meine war die empirische Antwort, seine war die sachkundige.
Poisson Aerohead
Siehe auch diese Frage, die zeigt, wie man das verwendet, stdbufwas auch hilfreich sein könnte. unix.stackexchange.com/questions/182537/…
Joe