grep non emette fino a EOF se convogliato attraverso cat


19

Dato questo esempio minimo

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

viene emesso LINE 1e quindi, dopo un secondo, viene emesso LINE 2, come previsto .


Se lo convogliamo a grep LINE

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

il comportamento è lo stesso del caso precedente, come previsto .


Se, in alternativa, lo convogliamo a cat

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

il comportamento è di nuovo lo stesso, come previsto .


Tuttavia , se eseguiamo il pipe verso grep LINEe quindi verso cat,

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

non c'è output fino a quando non passa un secondo ed entrambe le linee appaiono immediatamente sull'output, cosa che non mi aspettavo .


Perché sta succedendo e come posso fare in modo che l'ultima versione si comporti allo stesso modo dei primi tre comandi?


catconcatena i file. Cosa stai cercando di fare collegando cat?
Douglas Held,

15
@DouglasHeld Se chiamato senza argomenti, catlegge stdine genera semplicemente stdout. Naturalmente, mi sono posto questa domanda con molte cose complesse al posto di echoe cat, ma queste si sono rivelate irrilevanti, poiché il problema si presenta con esempi molto più semplici.
Lisyarus,

3
@DouglasHeld: il piping su cat è spesso utile per forzare lo stdout a non essere un terminale. Ad esempio, questo è un modo semplice per ottenere molti comandi per non usare l'output colorato.
wchargin,

Giuro che questo è un duplicato di un'altra domanda su Stack Overflow!
iBug

@wchargin grazie mille, mi hai insegnato qualcosa di nuovo su posix che non ho mai saputo.
Douglas tenuto il

Risposte:


38

Quando l' grepoutput di (almeno GNU) non è un terminale, ne bufferizza l'output, che è ciò che causa il comportamento che stai vedendo. Puoi disabilitarlo usando grepl' --line-bufferedopzione GNU :

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

o l' stdbufutilità:

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

La disattivazione del buffering in pipe contiene ulteriori informazioni su questo argomento.


26

Spiegazione semplificata

Come molte utility, non essendo qualcosa di peculiare per un programma, grepvaria l'output standard tra il buffer di linea e il buffer completo . Nel primo caso, la libreria C buffer esegue l'output dei dati in memoria fino a quando il buffer contenente tali dati non viene riempito o non viene aggiunto un carattere di avanzamento riga (o il programma termina in modo pulito), dopodiché chiama write()per scrivere effettivamente il contenuto del buffer. In quest'ultimo caso, solo il buffer in memoria che si riempie (o che il programma termina in modo pulito) attiva ilwrite() .

Spiegazione più dettagliata

Questa è la spiegazione ben nota, ma leggermente sbagliata. In effetti, l'output standard non è bufferizzato in linea ma smart buffer nella libreria GNU C e nella libreria BSD C. Uscita standard è anche ripulita quando la lettura standard di ingresso esaurisce suo buffer nella memoria (di ingresso pre-lettura) e la libreria C deve chiamare read()per recuperare alcuni più ingressi ed è leggendo l'inizio di una nuova linea. (Una ragione di ciò è prevenire il deadlock quando un altro programma si connette a entrambe le estremità di un filtro e si aspetta di essere in grado di operare riga per riga, alternando la scrittura al filtro e la lettura da esso; come "coprocessi" in GNU awkper esempio.)

Influenza della biblioteca C.

grepe le altre utility fanno questo - o, più rigorosamente, le librerie C che usano lo fanno, perché questa è una caratteristica definita della programmazione nel linguaggio C - in base a ciò che rilevano il loro output standard. Se (e solo se) non è un dispositivo interattivo, scelgono il buffering completo, altrimenti scelgono il buffering intelligente. Una pipe non è considerata un dispositivo interattivo, poiché la definizione di dispositivo interattivo, almeno nel mondo di Unix e Linux, è essenzialmente la isatty()chiamata che ritorna vera per il descrittore di file pertinente.

Soluzioni alternative per disabilitare il buffering completo

Alcune utility come grephanno opzioni idiosincratiche come quella --line-bufferedche cambiano questa decisione, che come puoi vedere è sbagliata. Ma una minima parte dei programmi di filtro che si potrebbe usare in realtà ha una tale opzione.

Più in generale, si possono usare strumenti che scavano negli interni specifici della libreria C e cambiano il suo processo decisionale (che hanno problemi di sicurezza se il programma da modificare è set-UID, e sono anche specifici per particolari librerie C, e in effetti lo sono specifico per i programmi scritti o sovrapposti al linguaggio C) o strumenti come quelli ptybandageche non cambiano gli interni del programma ma semplicemente interpongono uno pseudo-terminale come output standard in modo che la decisione venga presa come "interattiva", per influenza questo.

Ulteriori letture


1
Se la frase "line buffered" è un termine improprio, non è proprio colpa di grep, ma delle chiamate della libreria sottostante, setbuf/setvbuf . Non conosco un riferimento online affidabile per lo standard C, ma ad esempio le pagine man Linux e FreeBSD insieme alla descrizione POSIX di setvbufchiamarlo "line buffered". Persino la costante simbolica è _IOLBF.
ilkkachu,

Bene, ora hai imparato meglio. Questa strategia di buffering è descritta nel doco della libreria GNU C, anche se brevemente. Laurent Bercot è più diretto sulla questione. Ne ho parlato anche io.
JdeBP,

Non pensavo che "la tua aspettativa è sbagliata" era una buona voce per questa eccellente spiegazione del buffering dell'output. Spero non ti dispiaccia che l'ho rimosso e aggiunto alcune intestazioni descrittive per ogni sezione della risposta.
Anthony G - giustizia per Monica,

2
@ilkkachu Lo standard C utilizza effettivamente "buffering di linea". Per 7.21.3 Files , paragrafo 3 : "Quando uno stream non è bufferizzato, ... Quando uno stream è completamente bufferizzato, ... Quando uno stream è bufferizzato in linea, i caratteri sono destinati a essere trasmessi da o verso l'ambiente host come quando si incontra un carattere di nuova riga ... "In effetti, lo standard C usa cinque volte l'esatta frase" buffer di linea ". Quindi non è un termine improprio.
Andrew Henle,

1
Inoltre, l'approccio qui descritto come "buffering intelligente", a quanto ho capito, sembra essere proprio quello che lo standard C descrive come "buffering di linea". In particolare, oltre a svuotare il buffer su newline, "Quando un flusso è bufferizzato in linea, i caratteri sono destinati a essere trasmessi da o verso l'ambiente host come un blocco quando [...] è richiesto l'input su un flusso senza buffer o quando l'input è richiesto su un flusso con buffer di linea che richiede la trasmissione di caratteri dall'ambiente host. " Quindi questa non è una stranezza GNU o BSD, ma piuttosto ciò che la lingua richiede.
John Bollinger,

7

Uso

grep --line-buffered

per rendere grep non bufferizzato più di una riga alla volta.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.