Sie verschachteln sich! Sie haben nur kurze Output-Bursts ausprobiert, die ungeteilt bleiben. In der Praxis kann jedoch nur schwer garantiert werden, dass ein bestimmter Output ungeteilt bleibt.
Ausgabepufferung
Es kommt darauf an, wie die Programme ihre Ausgabe puffern . Die stdio-Bibliothek , die die meisten Programme beim Schreiben verwenden, verwendet Puffer, um die Ausgabe effizienter zu gestalten. Anstatt Daten auszugeben, sobald das Programm eine Bibliotheksfunktion zum Schreiben in eine Datei aufruft, speichert die Funktion diese Daten in einem Puffer und gibt sie tatsächlich erst dann aus, wenn der Puffer voll ist. Dies bedeutet, dass die Ausgabe in Chargen erfolgt. Genauer gesagt gibt es drei Ausgabemodi:
- Unbuffered: Die Daten werden sofort ohne Verwendung eines Puffers geschrieben. Dies kann langsam sein, wenn das Programm seine Ausgabe in kleinen Stücken schreibt, z. B. zeichenweise. Dies ist der Standardmodus für Standardfehler.
- Voll gepuffert: Die Daten werden nur geschrieben, wenn der Puffer voll ist. Dies ist der Standardmodus beim Schreiben in eine Pipe oder in eine reguläre Datei, außer mit stderr.
- Zeilenweise gepuffert: Die Daten werden nach jeder neuen Zeile oder wenn der Puffer voll ist, geschrieben. Dies ist der Standardmodus beim Schreiben in ein Terminal, außer mit stderr.
Programme können jede Datei neu programmieren, um sich anders zu verhalten, und können den Puffer explizit leeren. Der Puffer wird automatisch geleert, wenn ein Programm die Datei schließt oder normal beendet.
Wenn alle Programme, die in dieselbe Pipe schreiben, entweder den zeilengepufferten Modus oder den ungepufferten Modus verwenden und jede Zeile mit einem einzigen Aufruf an eine Ausgabefunktion schreiben und die Zeilen kurz genug sind, um in einen einzelnen Block zu schreiben, dann Die Ausgabe ist eine Verschachtelung ganzer Zeilen. Wenn jedoch eines der Programme den vollständig gepufferten Modus verwendet oder die Zeilen zu lang sind, werden gemischte Zeilen angezeigt.
Hier ist ein Beispiel, in dem ich die Ausgabe von zwei Programmen verschachtele. Ich habe GNU Coreutils unter Linux verwendet. Verschiedene Versionen dieser Dienstprogramme können sich unterschiedlich verhalten.
yes aaaa
schreibt aaaa
für immer in einem Modus, der im Wesentlichen dem zeilengepufferten Modus entspricht. Das yes
Dienstprogramm schreibt tatsächlich mehrere Zeilen gleichzeitig. Bei jeder Ausgabe werden jedoch eine ganze Reihe von Zeilen ausgegeben.
echo bbbb; done | grep b
Schreibt bbbb
für immer im vollständig gepufferten Modus. Es verwendet eine Puffergröße von 8192 und jede Zeile ist 5 Byte lang. Da 5 8192 nicht teilt, befinden sich die Grenzen zwischen Schreibvorgängen im Allgemeinen nicht an einer Liniengrenze.
Lassen Sie uns sie zusammen werfen.
$ { yes aaaa & while true; do echo bbbb; done | grep b & } | head -n 999999 | grep -e ab -e ba
bbaaaa
bbbbaaaa
baaaa
bbbaaaa
bbaaaa
bbbaaaa
ab
bbbbaaa
Wie man sieht, wird ja manchmal grep unterbrochen und umgekehrt. Nur etwa 0,001% der Leitungen wurden unterbrochen, aber es ist passiert. Die Ausgabe ist randomisiert, sodass die Anzahl der Unterbrechungen variiert, aber ich habe jedes Mal mindestens ein paar Unterbrechungen gesehen. Wenn die Leitungen länger wären, gäbe es einen höheren Anteil unterbrochener Leitungen, da die Wahrscheinlichkeit einer Unterbrechung zunimmt, wenn die Anzahl der Leitungen pro Puffer abnimmt.
Es gibt verschiedene Möglichkeiten, die Ausgabepufferung anzupassen . Die wichtigsten sind:
- Deaktivieren Sie die Pufferung in Programmen, die die stdio-Bibliothek verwenden, ohne die Standardeinstellungen mit dem Programm zu ändern
stdbuf -o0
sich in GNU coreutils und einigen anderen Systemen wie FreeBSD befindet. Alternativ können Sie mit auf Zeilenpufferung umschalten stdbuf -oL
.
- Wechseln Sie zur Zeilenpufferung, indem Sie die Programmausgabe über ein Terminal leiten, das nur für diesen Zweck mit erstellt wurde
unbuffer
. Einige Programme verhalten sich möglicherweise anders. Beispielsweise werden grep
standardmäßig Farben verwendet, wenn es sich bei der Ausgabe um ein Terminal handelt.
- Konfigurieren Sie das Programm zum Beispiel durch Übergeben
--line-buffered
an GNU grep übergeben.
Sehen wir uns den obigen Ausschnitt noch einmal an, diesmal mit Zeilenpufferung auf beiden Seiten.
{ stdbuf -oL yes aaaa & while true; do echo bbbb; done | grep --line-buffered b & } | head -n 999999 | grep -e ab -e ba
abbbb
abbbb
abbbb
abbbb
abbbb
abbbb
abbbb
abbbb
abbbb
abbbb
abbbb
abbbb
abbbb
Also diesmal hat ja grep nie unterbrochen, aber grep hat ja manchmal unterbrochen. Ich werde später kommen, warum.
Pipe-Interleaving
Solange jedes Programm jeweils eine Zeile ausgibt und die Zeilen kurz genug sind, werden die Ausgabezeilen sauber getrennt. Aber es gibt eine Grenze, wie lange die Leitungen sein können, damit dies funktioniert. Die Pipe selbst hat einen Transferpuffer. Wenn ein Programm auf einer Pipe ausgegeben wird, werden die Daten vom Schreibprogramm in den Übertragungspuffer der Pipe und später vom Übertragungspuffer der Pipe in das Leseprogramm kopiert. (Zumindest konzeptionell - der Kernel kann dies manchmal zu einer einzigen Kopie optimieren.)
Wenn mehr Daten kopiert werden müssen, als in den Übertragungspuffer der Pipe passen, kopiert der Kernel jeweils einen Puffer. Wenn mehrere Programme in dieselbe Pipe schreiben und das erste Programm, das der Kernel auswählt, mehr als ein Pufferprogramm schreiben möchte, kann nicht garantiert werden, dass der Kernel das gleiche Programm beim zweiten Mal erneut auswählt. Wenn beispielsweise P die Puffergröße ist, foo
2 · P Bytes bar
schreiben und 3 Bytes schreiben möchte, dann ist eine mögliche Verschachtelung P Bytes von foo
, dann 3 Bytes von bar
und P Bytes vonfoo
.
Wenn ich auf das obige yes + grep-Beispiel zurückkehre, werden auf meinem System yes aaaa
so viele Zeilen geschrieben, wie auf einmal in einen 8192-Byte-Puffer passen. Da 5 Bytes zu schreiben sind (4 druckbare Zeichen und die Newline), bedeutet dies, dass jedes Mal 8190 Bytes geschrieben werden. Die Pipe-Puffergröße beträgt 4096 Bytes. Es ist daher möglich, 4096 Bytes von yes abzurufen, dann eine Ausgabe von grep und dann den Rest des Schreibvorgangs von yes (8190 - 4096 = 4094 Bytes). 4096 Bytes lassen Platz für 819 Zeilen mit aaaa
und einem Lone a
. Daher eine Zeile mit diesem Lone, a
gefolgt von einem Schreiben von grep, wobei eine Zeile mit gegeben wird abbbb
.
Wenn Sie die Details der Vorgänge anzeigen möchten, getconf PIPE_BUF .
wird die Pipe-Puffergröße auf Ihrem System angezeigt und Sie können eine vollständige Liste der Systemaufrufe anzeigen, die von jedem Programm mit ausgeführt werden
strace -s9999 -f -o line_buffered.strace sh -c '{ stdbuf -oL yes aaaa & while true; do echo bbbb; done | grep --line-buffered b & }' | head -n 999999 | grep -e ab -e ba
So stellen Sie eine saubere Linienverschachtelung sicher
Wenn die Zeilenlängen kleiner als die Pipe-Puffergröße sind, stellt die Zeilenpufferung sicher, dass die Ausgabe keine gemischten Zeilen enthält.
Wenn die Zeilenlängen größer sein können, gibt es keine Möglichkeit, willkürliches Mischen zu vermeiden, wenn mehrere Programme auf dieselbe Pipe schreiben. Um die Trennung zu gewährleisten, müssen Sie jedes Programm in eine andere Pipe schreiben lassen und die Zeilen mit einem Programm kombinieren. Zum Beispiel macht GNU Parallel dies standardmäßig.