grep wird erst dann ausgegeben, wenn EOF durch cat geleitet wird

19

Angesichts dieses minimalen Beispiels

( echo "LINE 1" ; sleep 1 ; echo "LINE 2" ; )

es gibt LINE 1und dann, nach einer Sekunde, Ausgänge LINE 2, wie erwartet .


Wenn wir das weiterleiten grep LINE

( echo "LINE 1" ; sleep 1 ; echo "LINE 2" ; ) | grep LINE

Das Verhalten ist das gleiche wie im vorherigen Fall, wie erwartet .


Wenn wir dies alternativ weiterleiten cat

( echo "LINE 1" ; sleep 1 ; echo "LINE 2" ; ) | cat

Das Verhalten ist wieder das gleiche, wie erwartet .


Allerdings , wenn wir Rohr grep LINE, und dann cat,

( echo "LINE 1" ; sleep 1 ; echo "LINE 2" ; ) | grep LINE | cat

Es erfolgt keine Ausgabe, bis eine Sekunde verstrichen ist, und beide Zeilen erscheinen sofort auf der Ausgabe, was ich nicht erwartet hatte .


Warum passiert das und wie kann ich die letzte Version so einstellen, dass sie sich wie die ersten drei Befehle verhält?

Lisyarus
quelle
catverkettet Dateien. Was versuchst du zu tun, indem du hineinpfeifst cat?
Douglas Held
15
@DouglasHeld Bei Aufruf ohne Argumente cateinfach einlesen stdinund ausgeben stdout. Natürlich habe ich mir diese Frage mit einer Menge komplexer Sachen anstelle von echound ausgedacht cat, aber diese erwiesen sich als irrelevant, da das Problem mit viel einfacheren Beispielen auftaucht.
Lisyarus
3
@DouglasHeld: Das Leiten zu cat ist oft nützlich, um stdout zu zwingen, kein Terminal zu sein. Dies ist beispielsweise eine einfache Methode, um viele Befehle dazu zu bringen, keine farbigen Ausgaben zu verwenden.
Wchargin
Ich schwöre, dies ist ein Duplikat einer anderen Frage zum Stapelüberlauf !
iBug
@wchargin vielen dank, du hast mir etwas neues über posix beigebracht, das ich nie gekannt habe.
Douglas Held

Antworten:

38

Wenn die grepAusgabe von (mindestens GNU) kein Terminal ist, puffert sie die Ausgabe. Dies ist der Grund für das Verhalten, das Sie sehen. Sie können dies entweder mit der GNU grep- --line-bufferedOption deaktivieren :

( echo "LINE 1" ; sleep 1 ; echo "LINE 2" ; ) | grep --line-buffered LINE | cat

oder das stdbufDienstprogramm:

( echo "LINE 1" ; sleep 1 ; echo "LINE 2" ; ) | stdbuf -oL grep LINE | cat

Das Ausschalten der Pufferung in der Pipe hat mehr zu diesem Thema.

Stephen Kitt
quelle
26

Vereinfachte Erklärung

Wie bei vielen Dienstprogrammen grepvariiert auch dies, da es sich nicht um eine Besonderheit eines Programms handelt, die Standardausgabe zwischen Zeilen- und Vollpufferung . Im ersteren Fall puffert die C-Bibliothek die Ausgabedaten im Speicher, bis entweder der Puffer, der diese Daten enthält, gefüllt ist oder ein Zeilenvorschubzeichen hinzugefügt ist (oder das Programm sauber endet), woraufhin es aufruft write(), den Pufferinhalt tatsächlich zu schreiben. Im letzteren Fall löst nur der In-Memory-Puffer, der voll wird (oder das Programm endet sauber), den aus write().

Detailliertere Erklärung

Dies ist die bekannte, aber leicht falsche Erklärung. Tatsächlich wird die Standardausgabe in der GNU C-Bibliothek und der BSD C-Bibliothek nicht zeilen-, sondern intelligent gepuffert . Standardausgabe ist auch gespült , wenn Standard - Leseeingangs erschöpft seine in-Speicherpuffer (der Vor-Lese - Eingang) und die C - Bibliothek aufrufen read()etwas mehr Eingabe zu holen und es ist , den Anfang einer neuen Zeile zu lesen. (Ein Grund dafür ist, ein Deadlock zu verhindern, wenn sich ein anderes Programm mit beiden Enden eines Filters verbindet und erwartet, dass es zeilenweise abwechselnd auf den Filter schreiben und von ihm lesen kann, wie "Coprozesse" in GNU awkbeispielsweise.)

C Bibliothekseinfluss

grepund die anderen Dienstprogramme tun dies - oder genauer gesagt die C-Bibliotheken, die sie verwenden -, da dies eine definierte Funktion der Programmierung in der C-Sprache ist - basierend auf dem, was sie als Standardausgabe erkennen. Wenn (und nur wenn) es sich nicht um ein interaktives Gerät handelt, wählen sie die vollständige Pufferung, andernfalls wählen sie die intelligente Pufferung. Eine Pipe wird nicht als interaktives Gerät betrachtet, da die Definition eines interaktiven Geräts, zumindest in der Welt von Unix und Linux, im Wesentlichen die isatty()Rückgabe von true für den entsprechenden Dateideskriptor ist.

Problemumgehungen zum Deaktivieren der vollständigen Pufferung

Einige Dienstprogramme wie grephaben eigenwillige Optionen wie --line-buffereddiese, die diese Entscheidung ändern, was, wie Sie sehen, falsch benannt ist. Aber ein verschwindend kleiner Teil der Filterprogramme, die man verwenden könnte, hat tatsächlich eine solche Option.

Im Allgemeinen kann man Tools verwenden, die in die spezifischen Interna der C-Bibliothek eingreifen und deren Entscheidungsfindung ändern (die Sicherheitsprobleme aufweisen, wenn das zu ändernde Programm eine Set-UID hat, und die auch für bestimmte C-Bibliotheken spezifisch sind und tatsächlich sind) B. Programme, die in der Programmiersprache C geschrieben sind oder über der Programmiersprache C liegen), oder Tools ptybandage, die die Interna des Programms nicht ändern, sondern einfach ein Pseudoterminal als Standardausgabe einfügen, damit die Entscheidung als "interaktiv" ausfällt beeinflussen dies.

Weitere Lektüre

JdeBP
quelle
1
Wenn die Phrase "line buffered" eine falsche Bezeichnung ist, dann ist es nicht wirklich die Schuld grepder zugrunde liegenden Bibliotheksaufrufe, setbuf/setvbuf . Ich kenne keine verlässliche Online-Referenz für den C-Standard, aber z. B. die Linux- und FreeBSD-Handbuchseiten zusammen mit der POSIX-Beschreibung von setvbufcall it "line buffered". Sogar die symbolische Konstante dafür ist _IOLBF.
Ilkkachu
Nun hast du es besser gelernt. Diese Pufferstrategie wird in der GNU C-Bibliothek doco beschrieben, wenn auch kurz. Laurent Bercot ist in dieser Angelegenheit klarer. Ich habe es auch erwähnt.
JdeBP
Ich dachte nicht, dass "Ihre Erwartung falsch ist" eine gute Überschrift für diese hervorragende Erklärung der Ausgabepufferung ist. Ich hoffe, es macht Ihnen nichts aus, dass ich es entfernt und einige beschreibende Überschriften für jeden Abschnitt der Antwort hinzugefügt habe.
Anthony G - Gerechtigkeit für Monica
2
@ilkkachu Der C-Standard verwendet in der Tat "zeilengepuffert". Pro 7.21.3 Dateien , Absatz 3 : „Wenn ein Strom ungepufferte ist, ... Wenn ein Strom vollständig gepuffert, ... Wenn eine Stromleitung gepuffert ist, Zeichen sollen oder von der Host - Umgebung als einen zu übertragenden blockieren, wenn ein Zeichen in einer neuen Zeile auftritt. Es ist also keine Fehlbezeichnung.
Andrew Henle
1
Darüber hinaus scheint der hier als "intelligentes Puffern" beschriebene Ansatz genau das zu sein, was der C-Standard als "Zeilenpuffern" beschreibt. Zusätzlich zum Leeren des Puffers in Zeilenumbrüchen: "Wenn ein Stream zeilenweise gepuffert ist, sollen Zeichen als Block an die oder von der Host-Umgebung übertragen werden, wenn [...] Eingaben in einen ungepufferten Stream angefordert werden, oder wenn Die Eingabe wird in einem zeilengepufferten Stream angefordert, für den die Übertragung von Zeichen aus der Host-Umgebung erforderlich ist. " Das ist also keine GNU- oder BSD-Eigenheit, sondern das, was die Sprache verlangt.
John Bollinger
7

Verwenden

grep --line-buffered

Damit grep nicht mehr als eine Zeile gleichzeitig puffert.

Choroba
quelle