Tubi, come fluiscono i dati in una pipeline?


22

Non capisco come i dati scorrono nella pipeline e spero che qualcuno possa chiarire cosa sta succedendo lì.

Ho pensato che una pipeline di comandi elabora i file (testo, matrici di stringhe) in modo riga per riga. (Se ogni comando stesso funziona riga per riga.) Ogni riga di testo passa attraverso la pipeline, i comandi non attendono che il precedente finisca di elaborare l'intero input.

Ma sembra che non sia così.

Ecco un esempio di test. Ci sono alcune righe di testo. Li ho maiuscoli e ripeto ogni riga due volte. Lo faccio con cat text | tr '[:lower:]' '[:upper:]' | sed 'p'.

Per seguire il processo possiamo eseguirlo "in modo interattivo" - saltare il nome del file di input in cat. Ogni parte della pipeline funziona riga per riga:

$ cat | tr '[:lower:]' '[:upper:]'
alkjsd
ALKJSD
sdkj
SDKJ
$ cat | sed 'p'
line1
line1
line1
line 2
line 2
line 2

Ma la pipeline completa non aspetta che finisca l'input EOFe solo dopo stampa il risultato:

$ cat | tr '[:lower:]' '[:upper:]' | sed 'p'
I am writing...
keep writing...
now ctrl-D
I AM WRITING...
I AM WRITING...
KEEP WRITING...
KEEP WRITING...
NOW CTRL-D
NOW CTRL-D

Dovrebbe essere così? Perché non è riga per riga?


Non è la pipa, è catbuffering fino alla chiusura di stdin.
riccioli d'oro

ma tre sedfare linee di processo catprima della chiusura di stdin
xealits

Le impostazioni predefinite utilizzate da stdio (che credo siano utilizzate da tutti i programmi citati) è che stderr non è bufferizzato e stdout è bufferizzato in linea durante la scrittura su un terminale e completamente bufferizzato altrimenti (ad esempio se si sta scrivendo su un file o una pipe) . Alcuni comandi hanno flag che possono cambiare il buffering stdout, ma sembra che tr non lo faccia.
Kasperd,

Risposte:


36

Esiste una regola di buffering generale seguita dalla libreria I / O standard C ( stdio) utilizzata dalla maggior parte dei programmi unix. Se l'uscita sta per un terminale, viene scaricata alla fine di ogni riga; altrimenti viene scaricato solo quando il buffer (8K sul mio sistema Linux / amd64; potrebbe essere diverso sul tuo) è pieno.

Se tutte le utilità seguivano la regola generale, si dovrebbe vedere l'uscita in ritardo in tutti i tuoi esempi ( cat|sed, cat|tr, e cat|tr|sed). Ma c'è un'eccezione: GNU catnon bufferizza mai il suo output. Non utilizza stdioo modifica la stdiopolitica di buffering predefinita .

Posso essere abbastanza sicuro che stai usando GNU cate non qualche altro unix catperché gli altri non si comporteranno in questo modo. Unix tradizionale catha -uun'opzione per richiedere output senza buffer. GNU catignora l' -uopzione perché il suo output è sempre senza buffer.

Quindi ogni volta che si ha una pipe con a catsulla sinistra, nel sistema GNU, il passaggio dei dati attraverso la pipe non sarà ritardato. Non catsta nemmeno andando riga per riga: il tuo terminale lo sta facendo. Mentre digiti input per cat, il tuo terminale è in modalità "canonica" - basata su linea, con tasti di modifica come backspace e ctrl-U che ti offrono la possibilità di modificare la linea che hai digitato prima di inviarlo con Enter.

Nel cat|tr|sedesempio, trè ancora ricevere dati da catappena si preme Enter, ma trsta seguendo il stdiocriterio predefinito: la sua uscita sta per un tubo, in modo che non filo dopo ogni riga. Scrive sulla seconda pipe quando il buffer è pieno o quando viene ricevuto un EOF, a seconda di quale evento si verifica per primo.

sedsta anche seguendo la stdiopolitica di default, ma il suo output sta andando su un terminale, quindi scriverà ogni riga non appena avrà finito con esso. Questo ha un effetto su quanto devi digitare prima che qualcosa appaia sull'altra estremità della pipeline - se il sedbuffering del blocco fosse il suo output, dovresti digitare il doppio (per riempire tril buffer di output e sed l'output buffer).

GNU sedha -uun'opzione, quindi se si inverte l'ordine e si utilizza cat|sed -u|tr, l'output verrà visualizzato di nuovo immediatamente. (L' sed -uopzione potrebbe essere disponibile altrove ma non credo che sia un'antica tradizione unix come cat -u) Per quanto posso dire non esiste un'opzione equivalente per tr.

Esiste un'utilità chiamata stdbufche consente di modificare la modalità di buffering di qualsiasi comando che utilizza le stdioimpostazioni predefinite. È un po 'fragile poiché utilizza LD_PRELOADper realizzare qualcosa che la libreria C non è stata progettata per supportare, ma in questo caso sembra funzionare:

cat | stdbuf -o 0 tr '[:lower:]' '[:upper:]' | sed 'p'

1
Grazie! Risposta fantastica. Probabilmente dovrei menzionare in qualche modo il buffering nella domanda, in modo che si possa trovare.
Xealits,

teee di ddsolito giocano anche secondo le proprie regole. Se combinati in modo fantasioso, i tre strumenti possono piuttosto negare in modo portabile qualsiasi necessità stdbufin pipeline in background.
Mikeserv,

1
Questo è uno dei motivi per evitare un uso inutile del gatto .
Hobbs

8

Questo in realtà mi ha preso qualche pensiero per capire e ancora di più per rispondere. Ottima domanda (la voterò successivamente).

Hai trascurato di provare tr | sednei tuoi articoli di debug sopra:

>tr '[:lower:]' '[:upper:]' | sed 'p'
i am writing
still writing
now ctrl-d
I AM WRITING
I AM WRITING
STILL WRITING
STILL WRITING
NOW CTRL-D
NOW CTRL-D
>

Quindi evidentemente trbuffer. Impara qualcosa di nuovo ogni giorno!

MODIFICA :

Mentre ci penso sopra, abbiamo isolato la causa, ma non abbiamo fornito una spiegazione. Se si cat | tr, si scrive subito, se cat | sed, scrive subito, ma se tr | sedsi attende per EOF. Vorrei suggerire che la risposta potrebbe essere sepolta nel codice sorgente tro sedallora, e non essere un problema di pipe.

MODIFICA :

Vedo che Wumpus ha fornito la spiegazione mentre scrivevo l'ultima modifica. Grazie!


1
anzi tamponano! e il test con linee di circa 8kb, come menzionato da Wumpus, mostra che il buffer è effettivamente di 8Kb. Vorrei accettare entrambe le risposte per condividere un po 'di reputazione, ma prenderò quella di Wumpus come più completa. Grazie comunque!
Xealits,

1
Nessun problema, la mia era la risposta empirica, la sua era quella ben informata.
Poisson Aerohead,

Vedi anche questa domanda che mostra come usare stdbufche potrebbe anche essere utile. unix.stackexchange.com/questions/182537/…
Joe
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.