Forza il buffer di linea di stdout durante il collegamento a tee


117

Di solito stdoutè con buffer di riga. In altre parole, finché il tuo printfargomento finisce con una nuova riga, puoi aspettarti che la riga venga stampata immediatamente. Questo non sembra essere valido quando si utilizza una pipe a cui reindirizzare tee.

Ho un programma C ++ a, che restituisce stringhe, sempre \nterminate, a stdout.

Quando viene eseguito da solo ( ./a), tutto viene stampato correttamente e al momento giusto, come previsto. Tuttavia, se lo installo in tee( ./a | tee output.txt), non stampa nulla finché non si chiude, il che vanifica lo scopo dell'uso tee.

So che potrei risolverlo aggiungendo una fflush(stdout)dopo ogni operazione di stampa nel programma C ++. Ma esiste un modo più semplice e più pulito? C'è un comando che posso eseguire, ad esempio, che costringerebbe stdoutad essere bufferizzato di riga, anche quando si utilizza una pipe?

Risposte:


67

Prova unbufferche fa parte diexpect pacchetto. Potresti già averlo sul tuo sistema.

Nel tuo caso lo useresti in questo modo:

./a | unbuffer -p tee output.txt

( -pè per la modalità pipeline in cui unbuffer legge da stdin e lo passa al comando nel resto degli argomenti)


Grazie, ha funzionato, anche se ho dovuto compilare expectme stesso in quanto unbuffernon sembra essere incluso di default in OS X.
houbysoft

@houbysoft: sono contento che abbia funzionato per te. unbufferè solo un piccolo script, quindi non avresti dovuto dover ricompilare l'intero pacchetto.
In pausa fino a nuovo avviso.

Sì, probabilmente no, ma ci sono ./configure && makevoluti circa 10 secondi e poi sono passato unbuffera /usr/local/bin:)
houbysoft

3
L'ho installato sul mio Mac (10.8.5) tramite brew: brew install wait --with-brewed-tk
Nils

2
FWIW, poiché unbuffer è un po 'confuso, la struttura rilevante lo è unbuffer {commands with pipes/tee}.
Fake Name

128

Puoi provare stdbuf

$ stdbuf -o 0 ./a | tee output.txt

(grande) parte della pagina man:

  -i, --input=MODE   adjust standard input stream buffering
  -o, --output=MODE  adjust standard output stream buffering
  -e, --error=MODE   adjust standard error stream buffering

If MODE is 'L' the corresponding stream will be line buffered.
This option is invalid with standard input.

If MODE is '0' the corresponding stream will be unbuffered.

Otherwise MODE is a number which may be followed by one of the following:
KB 1000, K 1024, MB 1000*1000, M 1024*1024, and so on for G, T, P, E, Z, Y.
In this case the corresponding stream will be fully buffered with the buffer
size set to MODE bytes.

tienilo a mente, però:

NOTE: If COMMAND adjusts the buffering of its standard streams ('tee' does
for e.g.) then that will override corresponding settings changed by 'stdbuf'.
Also some filters (like 'dd' and 'cat' etc.) dont use streams for I/O,
and are thus unaffected by 'stdbuf' settings.

non è in esecuzione stdbufsu tee, si sta eseguendo su a, quindi questo non dovrebbe influire, se non si imposta il buffer di a'flussi s in a' s fonte.

Inoltre, nonstdbuf è POSIX, ma fa parte di GNU-coreutils.


3
Grazie, ma questo non sembra essere disponibile su OS X (la domanda è etichettata osx-lion).
houbysoft

2
@houbysoft - Sono abbastanza sicuro che gli strumenti GNU possano essere installati su OS X
jordanm

1
@jordanm: forse, ma l'installazione dell'intero strumento GNU sembra eccessivo per questo ...
houbysoft

1
Ho votato positivamente questa risposta perché stdbufè già disponibile sulle distribuzioni Centos Linux che stiamo utilizzando e unbuffernon lo è. Grazie!
Huw Walters

6
Per lo script python, stdbuf non funzionerà, ma è possibile utilizzare -uper disabilitare il buffering sul lato di python:python3 -u a.py | tee output.txt
Honza

27

Puoi anche provare a eseguire il tuo comando in uno pseudo-terminale usando il scriptcomando (che dovrebbe applicare l'output con buffer di riga alla pipe)!

script -q /dev/null ./a | tee output.txt     # Mac OS X, FreeBSD
script -c "./a" /dev/null | tee output.txt   # Linux

Tenere presente che il scriptcomando non si propaga allo stato di uscita del comando avvolto.


3
script -t 1 /path/to/outputfile.txt ./aha funzionato alla grande per il mio caso d'uso. Trasmette tutto l'output in outputfile.txttempo reale mentre lo stampa anche sullo stdout della shell. Non è stato necessario utilizzaretee
Peter Berg

26

Puoi usare setlinebuf da stdio.h.

setlinebuf(stdout);

Questo dovrebbe modificare il buffering in "buffer di riga".

Se hai bisogno di maggiore flessibilità puoi usare setvbuf.


8
Mi chiedo perché questa soluzione abbia così pochi voti positivi. Questa è l'unica soluzione che non impone un peso al chiamante.
oxygene

1
Nota che questo non è lo standard C (o anche POSIX). Probabilmente è meglio usare setvbuf(stdout, NULL, _IOLBF, 0), che è esattamente equivalente.
rvighne

Questo ha risolto il mio problema su OS X Catalina con un programma C ++ che era printf () ing e stavo andando verso il tee ma vedevo l'output solo quando il programma era finito.
jbaxter

2

Se invece usi le classi stream C ++, every std::endl è un flush implicito. Utilizzando la stampa in stile C, penso che il metodo che hai suggerito ( fflush()) sia l'unico modo.


4
Sfortunatamente, non è vero. È possibile osservare lo stesso comportamento con c ++ std :: cout anche quando si utilizza std :: endl o std :: flush. Il buffering avviene in primo piano e la soluzione più semplice in Linux sembra essere setlinebuf (stdout); come la prima riga in main () quando sei l'autore del programma e usando le altre soluzioni di cui sopra quando non puoi cambiare il codice sorgente.
oxygene

1
@oxygene Questo non è vero. L'ho provato e endl scarica il buffer quando si collega al tee (a differenza di printf). Codice: #include <iostream> #include <unistd.h> int main(void) { std::cout << "1" << std::endl; sleep(1); std::cout << "2" << std::endl; }. endl scarica
Curtis Yallop
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.