Angenommen, ich führe einige Prozesse aus:
#!/usr/bin/env bash
foo &
bar &
baz &
wait;
Ich führe das obige Skript folgendermaßen aus:
foobarbaz | cat
Wenn einer der Prozesse nach stdout / stderr schreibt, verschachtelt sich die Ausgabe meines Erachtens nie - jede Zeile von stdio scheint atomar zu sein. Wie funktioniert das? Welches Dienstprogramm steuert, wie jede Zeile atomar ist?
Antworten:
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:
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
schreibtaaaa
für immer in einem Modus, der im Wesentlichen dem zeilengepufferten Modus entspricht. Dasyes
Dienstprogramm schreibt tatsächlich mehrere Zeilen gleichzeitig. Bei jeder Ausgabe werden jedoch eine ganze Reihe von Zeilen ausgegeben.echo bbbb; done | grep b
Schreibtbbbb
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.
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:
stdbuf -o0
sich in GNU coreutils und einigen anderen Systemen wie FreeBSD befindet. Alternativ können Sie mit auf Zeilenpufferung umschaltenstdbuf -oL
.unbuffer
. Einige Programme verhalten sich möglicherweise anders. Beispielsweise werdengrep
standardmäßig Farben verwendet, wenn es sich bei der Ausgabe um ein Terminal handelt.--line-buffered
an GNU grep übergeben.Sehen wir uns den obigen Ausschnitt noch einmal an, diesmal mit Zeilenpufferung auf beiden Seiten.
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 Bytesbar
schreiben und 3 Bytes schreiben möchte, dann ist eine mögliche Verschachtelung P Bytes vonfoo
, dann 3 Bytes vonbar
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 mitaaaa
und einem Lonea
. Daher eine Zeile mit diesem Lone,a
gefolgt von einem Schreiben von grep, wobei eine Zeile mit gegeben wirdabbbb
.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 werdenSo 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.
quelle
cat
atomar geschrieben wurden, so dass der cat-Prozess ganze Zeilen von entweder foo / bar / baz, aber nicht eine halbe Zeile von einer und eine halbe Zeile von einer anderen usw. erhält. Kann ich mit dem Bash-Skript etwas anfangen?awk
zwei (oder mehr) ausgabezeilen für die gleiche id mitfind -type f -name 'myfiles*' -print0 | xargs -0 awk '{ seen[$1]= seen[$1] $2} END { for(x in seen) print x, seen[x] }'
aberfind -type f -name 'myfiles*' -print0 | xargs -0 cat| awk '{ seen[$1]= seen[$1] $2} END { for(x in seen) print x, seen[x] }'
damit korrekt nur eine zeile für jede id erzeugt wurde.http://mywiki.wooledge.org/BashPitfalls#Non-atomic_writes_with_xargs_-P hat Folgendes untersucht:
quelle
xargs echo
Ruft nicht die eingebaute Echo-Bash auf, sondern dasecho
Dienstprogramm von$PATH
. Und trotzdem kann ich dieses Bash-Echo-Verhalten mit Bash 4.4 nicht reproduzieren. Unter Linux sind Schreibvorgänge in eine Pipe (not / dev / null), die größer als 4 KB ist, jedoch nicht garantiert atomar.