Disattiva il buffering nel pipe


395

Ho uno script che chiama due comandi:

long_running_command | print_progress

Le long_running_commandstampe sono un progresso ma non sono soddisfatto. Sto usando print_progressper renderlo più bello (vale a dire, stampo l'avanzamento in una sola riga).

Il problema: la connessione di una pipe allo stdout attiva anche un buffer 4K, al bel programma di stampa non si ottiene nulla ... niente ... niente ... molto ... :)

Come posso disabilitare il buffer 4K per il long_running_command(no, non ho il sorgente)?


1
Quindi quando esegui long_running_command senza piping puoi vedere correttamente gli aggiornamenti di avanzamento, ma quando il piping viene bufferizzato?

1
Sì, è esattamente quello che succede.
Aaron Digulla,

20
L'incapacità di un semplice modo di controllare il buffering è stata un problema per decenni. Ad esempio, vedi: marc.info/?l=glibc-bug&m=98313957306297&w=4, che in sostanza dice "Non posso farcela facendo questo ed ecco alcune trappole per giustificare la mia posizione"


1
In realtà è lo stdio non la pipa che provoca un ritardo in attesa di dati sufficienti. Le pipe hanno una capacità, ma non appena ci sono dati scritti sulla pipe, è immediatamente pronta per la lettura all'altra estremità.
Sam Watkins,

Risposte:


254

È possibile utilizzare il unbuffercomando (incluso nel expectpacchetto), ad es

unbuffer long_running_command | print_progress

unbuffersi connette long_running_commandtramite uno pseudoterminale (pty), che fa sì che il sistema lo tratti come un processo interattivo, quindi non utilizzando il buffering a 4 kiB nella pipeline che è la probabile causa del ritardo.

Per pipeline più lunghe, potrebbe essere necessario eseguire il buffering di ciascun comando (tranne quello finale), ad es

unbuffer x | unbuffer -p y | z

3
In effetti, l'uso di un pty per connettersi a processi interattivi è vero per aspettarsi in generale.

15
Quando si pipeline le chiamate a unbuffer, è necessario utilizzare l'argomento -p in modo che unbuffer legga da stdin.

26
Nota: sui sistemi debian, questo è chiamato expect_unbuffered è nel expect-devpacchetto, non nel expectpacchetto
bdonlan

4
@bdonlan: almeno su Ubuntu (basato su debian), expect-devfornisce entrambi unbuffere expect_unbuffer(il primo è un collegamento simbolico al secondo). I collegamenti sono disponibili dal expect 5.44.1.14-1(2009).
jfs,

1
Nota: sui sistemi Ubuntu 14.04.x, è anche nel pacchetto explore-dev.
Alexandre Mazel,

462

Un altro modo per stdbufscuoiare questo gatto è usare il programma, che fa parte del GNU Coreutils (anche FreeBSD ne ha uno proprio).

stdbuf -i0 -o0 -e0 command

Questo disattiva completamente il buffering per input, output ed errori. Per alcune applicazioni, il buffering di linea può essere più adatto per motivi di prestazioni:

stdbuf -oL -eL command

Si noti che funziona solo per il stdiobuffering ( printf(), fputs()...) per applicazioni collegate dinamicamente e solo se tale applicazione non regola il buffering dei suoi flussi standard da solo, anche se dovrebbe coprire la maggior parte delle applicazioni.


6
"unbuffer" deve essere installato in Ubuntu, che è all'interno del pacchetto: explore-dev che è 2MB ...
lepe

2
Funziona benissimo con l'installazione raspbian predefinita per la registrazione non bufferizzata. Ho trovato sudo stdbuff … commandopere anche se stdbuff … sudo commandnon l'ho fatto.
natevw,

20
@qdii stdbufnon funziona teeperché teesovrascrive i valori predefiniti impostati da stdbuf. Vedi la pagina del manuale di stdbuf.
ceving il

5
@lepe Stranamente, unbuffer ha dipendenze da x11 e tcl / tk, il che significa che in realtà ha bisogno di> 80 MB se lo stai installando su un server senza di loro.
jpatokal,

10
@qdii stdbufutilizza il LD_PRELOADmeccanismo per inserire la propria libreria caricata dinamicamente libstdbuf.so. Ciò significa che non funzionerà con questi tipi di eseguibili: con setuid o set di funzionalità file, collegati staticamente, non usando libc standard. In questi casi è meglio usare le soluzioni con unbuffer/ script/ socat. Vedi anche stdbuf con setuid / capacità .
pabouk,

75

Ancora un altro modo per attivare la modalità di output del buffering di linea per il long_running_commandè usare il scriptcomando che esegue il tuo long_running_commandin uno pseudo terminale (pty).

script -q /dev/null long_running_command | print_progress      # FreeBSD, Mac OS X
script -c "long_running_command" /dev/null | print_progress    # Linux

15
+1 bel trucco, dato che scriptè un comando così vecchio, dovrebbe essere disponibile su tutte le piattaforme simili a Unix.
Aaron Digulla,

5
è necessario anche -qsu Linux:script -q -c 'long_running_command' /dev/null | print_progress
jfs l'

1
Sembra che gli script leggano stdin, il che rende impossibile eseguire un tale long_running_commandin background, almeno quando avviato dal terminale interattivo. Per risolvere il problema, sono stato in grado di reindirizzare lo stdin /dev/null, dal momento che il mio long_running_commandnon lo usa stdin.
Haridsv,

1
Funziona anche su Android.
not2qubit

3
Uno svantaggio significativo: ctrl-z non funziona più (ovvero non riesco a sospendere lo script). Questo può essere risolto, ad esempio: echo | script sudo -c / usr / local / bin / ec2-snapshot-all / dev / null | ts, se non ti dispiace non poter interagire con il programma.
rlpowell,

66

Per grep, seded awkè possibile forzare l'uscita da linea di buffer. Puoi usare:

grep --line-buffered

Forza l'output del buffer di linea. Per impostazione predefinita, l'output è bufferizzato in linea quando l'output standard è un terminale e blocca il buffer in altro modo.

sed -u

Rendere buffer della linea di output.

Vedi questa pagina per maggiori informazioni: http://www.perkin.org.uk/posts/how-to-fix-stdio-buffering.html


51

Se è un problema con libc che modifica il suo buffering / flushing quando l'output non va su un terminale, dovresti provare socat . È possibile creare un flusso bidirezionale tra quasi ogni tipo di meccanismo I / O. Uno di questi è un programma biforcuto che parla con una pseudo tty.

 socat EXEC:long_running_command,pty,ctty STDIO 

Quello che fa è

  • creare una pseudo tty
  • fork long_running_command con il lato slave del pty come stdin / stdout
  • stabilire un flusso bidirezionale tra il lato master del pty e il secondo indirizzo (qui è STDIO)

Se questo ti dà lo stesso output di long_running_command, allora puoi continuare con una pipe.

Modifica: Wow Non ho visto la risposta più spenta! Bene, socat è comunque un ottimo strumento, quindi potrei semplicemente lasciare questa risposta


1
... e non sapevo di socat - sembra un po 'come Netcat, forse solo di più. ;) Grazie e +1.

3
Vorrei usare socat -u exec:long_running_command,pty,end-close -qui
Stéphane Chazelas il

20

Puoi usare

long_running_command 1>&2 |& print_progress

Il problema è che libc eseguirà il buffer di riga quando stdout su schermo e buffer completo quando stdout su un file. Ma nessun buffer per stderr.

Non penso che sia il problema con il buffer delle pipe, è tutto sulla politica del buffer di libc.


Hai ragione; la mia domanda è ancora: come posso influenzare la politica del buffer di libc senza ricompilare?
Aaron Digulla,

@ StéphaneChazelas fd1 verrà reindirizzato a stderr
Wang HongQin

@ StéphaneChazelas non capisco il tuo punto di discussione. per favore fai un test, funziona
Wang HongQin

3
OK, quello che sta succedendo è che con entrambi zsh(da dove |&viene adattato da csh) e bash, quando lo fai cmd1 >&2 |& cmd2, sia fd 1 che 2 sono collegati allo stdout esterno. Quindi funziona per impedire il buffering quando quello stdout esterno è un terminale, ma solo perché l'output non passa attraverso la pipe (quindi print_progressnon stampa nulla). Quindi è lo stesso di long_running_command & print_progress(tranne che stdin di print_progress è una pipe che non ha scrittore). È possibile verificare con ls -l /proc/self/fd >&2 |& catrispetto a ls -l /proc/self/fd |& cat.
Stéphane Chazelas,

3
Questo perché |&è abbreviato 2>&1 |, letteralmente. Così cmd1 |& cmd2è cmd1 1>&2 2>&1 | cmd2. Quindi, sia fd 1 che 2 finiscono per essere collegati allo stderr originale, e non rimane più nulla da scrivere sulla pipe. ( s/outer stdout/outer stderr/gnel mio commento precedente).
Stéphane Chazelas,

11

Una volta era il caso, e probabilmente è ancora il caso, che quando l'output standard è scritto su un terminale, per impostazione predefinita è buffer di riga - quando viene scritta una nuova riga, la riga viene scritta sul terminale. Quando l'output standard viene inviato a una pipe, viene bufferizzato completamente, quindi i dati vengono inviati al processo successivo nella pipeline solo quando il buffer I / O standard è pieno.

Questa è la fonte del problema. Non sono sicuro che ci sia molto da fare per risolverlo senza modificare il programma scrivendo nella pipe. È possibile utilizzare la setvbuf()funzione con il _IOLBFflag per mettere incondizionatamente stdoutin modalità buffer linea. Ma non vedo un modo semplice per imporlo su un programma. Oppure il programma può fare fflush()in punti appropriati (dopo ogni riga di output), ma si applica lo stesso commento.

Suppongo che se si sostituisse la pipe con uno pseudo-terminale, allora la libreria I / O standard penserebbe che l'output fosse un terminale (perché è un tipo di terminale) e allinea automaticamente il buffer. Questo è un modo complesso di trattare le cose, però.


7

So che questa è una vecchia domanda e aveva già molte risposte, ma se si desidera evitare il problema del buffer, provare qualcosa del tipo:

stdbuf -oL tail -f /var/log/messages | tee -a /home/your_user_here/logs.txt

Ciò genererà in tempo reale i registri e li salverà anche nel logs.txtfile e il buffer non influirà più sul tail -fcomando.


4
Questa sembra la seconda risposta: - /
Aaron Digulla l'

2
stdbuf è incluso in gnu coreutils (ho verificato sull'ultima versione 8.25). verificato che funziona su un Linux incorporato.
zhaorufei,

Dalla documentazione di stdbuf, NOTE: If COMMAND adjusts the buffering of its standard streams ('tee' does for example) then that will override corresponding changes by 'stdbuf'.
shrewmouse,

6

Non penso che il problema sia con la pipa. Sembra che il tuo lungo processo non stia scaricando il proprio buffer abbastanza frequentemente. Cambiare la dimensione del buffer della pipe sarebbe un trucco per aggirarlo, ma non penso che sia possibile senza ricostruire il kernel - qualcosa che non vorresti fare come hack, poiché probabilmente influenzerà molti altri processi.


18
La causa principale è che libc passa al buffering 4k se lo stdout non è un tty.
Aaron Digulla,

5
È molto interessante ! perché la pipe non causa alcun buffering. Forniscono il buffering, ma se leggi da una pipe, ottieni tutti i dati disponibili, non devi aspettare un buffer nella pipe. Quindi il colpevole sarebbe il buffering stdio nell'applicazione.

3

Secondo questo post qui , potresti provare a ridurre l'ulimit pipe a un singolo blocco da 512 byte. Certamente non disattiverà il buffering, ma bene, 512 byte è molto meno di 4K: 3


3

Analogamente alla risposta di Chad , puoi scrivere una piccola sceneggiatura come questa:

# save as ~/bin/scriptee, or so
script -q /dev/null sh -c 'exec cat > /dev/null'

Quindi utilizzare questo scripteecomando in sostituzione di tee.

my-long-running-command | scriptee

Purtroppo, non riesco a ottenere una versione come quella che funzioni perfettamente in Linux, quindi sembra limitata agli unix in stile BSD.

Su Linux, questo è vicino, ma non ricevi il tuo prompt al termine (fino a quando premi Invio, ecc.) ...

script -q -c 'cat > /proc/self/fd/1' /dev/null

Perché funziona? Lo "script" disattiva il buffering?
Aaron Digulla,

@Aaron Digulla: scriptemula un terminale, quindi sì, credo che disattivi il buffering. Inoltre, riecheggia ogni personaggio inviato ad esso, motivo per cui catviene inviato /dev/nullnell'esempio. Per quanto riguarda il programma in esecuzione all'interno script, sta parlando con una sessione interattiva. Credo che sia simile a expectquesto riguardo, ma scriptprobabilmente fa parte del tuo sistema di base.
jwd

Il motivo che utilizzo teeè inviare una copia del flusso a un file. Dove viene specificato il file scriptee?
Bruno Bronosky,

@BrunoBronosky: hai ragione, è un brutto nome per questo programma. In realtà non sta eseguendo un'operazione "a T". Sta solo disabilitando il buffering dell'output, secondo la domanda originale. Forse dovrebbe essere chiamato "scriptcat" (anche se non sta facendo concatenazione ...). Indipendentemente da ciò, è possibile sostituire il catcomando con tee myfile.txte si dovrebbe ottenere l'effetto desiderato.
jwd

2

Ho trovato questa soluzione intelligente: (echo -e "cmd 1\ncmd 2" && cat) | ./shell_executable

Questo fa il trucco. catleggerà l'input aggiuntivo (fino a EOF) e lo passerà alla pipe dopo che echoha inserito i suoi argomenti nel flusso di input di shell_executable.


2
In realtà, catnon vede l'output di echo; basta eseguire due comandi in una subshell e l'output di entrambi viene inviato nella pipe. Il secondo comando nella subshell ('cat') legge dallo stdin genitore / esterno, ecco perché funziona.
Aaron Digulla,

0

In base a ciò, la dimensione del buffer della pipe sembra essere impostata nel kernel e richiederebbe di ricompilare il kernel per modificarlo.


7
Credo che sia un buffer diverso.
Samuel Edwin Ward
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.