Nehmen wir an, Sie haben eine Pipeline wie die folgende:
$ a | b
Wenn b
die Verarbeitung von stdin beendet wird, füllt sich die Pipe nach einer Weile und Schreibvorgänge von a
bis zu ihrem stdout werden blockiert (bis entweder b
die Verarbeitung erneut beginnt oder sie stirbt).
Wenn ich dies vermeiden wollte, könnte ich versucht sein, ein größeres Rohr (oder einfacher buffer(1)
) wie folgt zu verwenden :
$ a | buffer | b
Das würde mir einfach mehr Zeit verschaffen, aber am Ende a
würde es irgendwann aufhören.
Was ich gerne hätte (für ein sehr spezifisches Szenario, das ich anspreche), wäre eine "undichte" Pipe, die, wenn sie voll ist, einige Daten (idealerweise zeilenweise) aus dem Puffer löscht, um a
fortzufahren Verarbeitung (wie Sie sich wahrscheinlich vorstellen können, sind die Daten, die in der Pipe fließen, entbehrlich, dh es b
ist weniger wichtig , dass die Daten von verarbeitet werden, als dass a
sie ohne Blockierung ausgeführt werden können).
Um es zusammenzufassen, ich hätte gerne so etwas wie einen begrenzten, undichten Puffer:
$ a | leakybuffer | b
Ich könnte es wahrscheinlich ganz einfach in jeder Sprache implementieren. Ich habe mich nur gefragt, ob mir etwas "gebrauchsfertiges" (oder so etwas wie ein Bash-Einzeiler) fehlt.
Hinweis: In den Beispielen verwende ich reguläre Pipes, aber die Frage gilt auch für Named Pipes
Während ich die folgende Antwort erhielt, entschied ich mich auch, den Befehl leakybuffer zu implementieren, da die einfache Lösung unten einige Einschränkungen aufwies: https://github.com/CAFxX/leakybuffer
Antworten:
Am einfachsten wäre es, ein Programm zu durchlaufen, das die nicht blockierende Ausgabe festlegt. Hier ist ein einfacher Perl-Oneliner (den Sie als Leakybuffer speichern können ), der dies tut:
so wird dein
a | b
:Was getan wird, ist die Eingabe zu lesen und in die Ausgabe zu schreiben (wie
cat(1)
), aber die Ausgabe ist nicht blockierend - was bedeutet, dass wenn der Schreibvorgang fehlschlägt, ein Fehler zurückgegeben wird und Daten verloren gehen, der Prozess jedoch mit der nächsten Eingabezeile fortgesetzt wird, da wir die Eingabe bequem ignorieren Error. Der Prozess ist wie gewünscht zeilengepuffert, siehe jedoch die folgenden Einschränkungen.Sie können zum Beispiel testen mit:
Sie erhalten eine
output
Datei mit verlorenen Zeilen (die genaue Ausgabe hängt von der Geschwindigkeit Ihrer Shell usw. ab) wie folgt :Sie sehen, wo die Shell danach Linien verloren hat
12773
, aber auch eine Anomalie - die Perl hatte nicht genug Puffer dafür12774\n
, aber dafür1277
hat sie genau das geschrieben - und so75610
beginnt die nächste Zahl nicht am Anfang der Zeile, was sie klein macht hässlich.Dies könnte verbessert werden, indem Perl erkannt wird, wenn der Schreibvorgang nicht vollständig erfolgreich war, und später versucht wird, den Rest der Zeile zu löschen, während neue eingehende Zeilen ignoriert werden. Dies würde jedoch das Perl-Skript erheblich komplizieren. Dies bleibt als Übung für der interessierte Leser :)
Update (für Binärdateien): Wenn Sie keine Zeilenumbrüche mit Zeilenumbrüchen (wie Protokolldateien oder ähnliches) verarbeiten, müssen Sie den Befehl geringfügig ändern. Andernfalls verbraucht Perl viel Speicher (abhängig davon, wie oft Zeilenumbrüche in Ihrer Eingabe erscheinen):
Es funktioniert auch korrekt für Binärdateien (ohne zusätzlichen Speicher zu verbrauchen).
Update2 - schönere Textdateiausgabe: Vermeiden von Ausgabepuffern (
syswrite
anstelle vonprint
):scheint für mich Probleme mit "zusammengeführten Linien" zu beheben:
(Hinweis: Man kann überprüfen, auf welchen Zeilen die Ausgabe geschnitten wurde mit:
perl -ne '$c++; next if $c==$_; print "$c $_"; $c=$_' output
oneliner)quelle
perl -w -MFcntl -e 'fcntl STDOUT,F_SETFL,O_WRONLY|O_NONBLOCK; while (<STDIN>) { print }' | aplay -t raw -f dat --buffer-size=16000
, scheint Perl ständig mehr Speicher zuzuweisen, bis er vom OOM-Manager beendet wird.dd
s‘dd oflag=nonblock status=none
.$| = 1
und Ihrsyswrite()
Ansatz verhindert in der Tat kurze Schreibvorgänge, solange die Zeilen relativ kurz sind.