Força o buffering de linha de saída padrão ao canalizar para o tee

116

Normalmente, stdouté buffer de linha. Em outras palavras, contanto que seu printfargumento termine com uma nova linha, você pode esperar que a linha seja impressa instantaneamente. Isso não parece se manter ao usar um pipe para redirecionar tee.

Eu tenho um programa C ++,, aque produz strings, sempre \nterminadas, para stdout.

Quando é executado sozinho ( ./a), tudo imprime corretamente e na hora certa, conforme o esperado. No entanto, se eu canalizá-lo para tee( ./a | tee output.txt), ele não imprimirá nada até que seja encerrado, o que anula o propósito de uso tee.

Eu sei que poderia corrigir isso adicionando um fflush(stdout)após cada operação de impressão no programa C ++. Mas existe uma maneira mais limpa e mais fácil? Existe um comando que posso executar, por exemplo, que forçaria o stdoutbuffer de linha, mesmo ao usar um pipe?

houbysoft
fonte

Respostas:

66

Experimente o unbufferque faz parte do expectpacote. Você já pode tê-lo em seu sistema.

No seu caso, você o usaria assim:

./a | unbuffer -p tee output.txt

( -pé para o modo pipeline em que o unbuffer lê stdin e o passa para o comando no restante dos argumentos)

Pausado até novo aviso.
fonte
Obrigado, funcionou, embora eu tive que expectme compilar, pois unbuffernão parece estar incluído por padrão no OS X.
houbysoft
@houbysoft: Estou feliz que funcionou para você. unbufferé apenas um pequeno script, então você não deve precisar recompilar o pacote inteiro.
Pausado até novo aviso.
Sim, provavelmente não, mas ./configure && makedemorou cerca de 10 segundos e depois mudei unbufferpara /usr/local/bin:)
houbysoft
3
Eu instalei no meu mac (10.8.5) via brew: brew install expect --with-brewed-tk
Nils
2
FWIW, porque unbuffer é um tanto confuso, a estrutura relevante é unbuffer {commands with pipes/tee}.
Nome falso de
127

podes tentar stdbuf

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

(grande) parte da página de manual:

  -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.

tenha isso em mente, porém:

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.

você não está correndo stdbuf em tee, você está executando-o em a, de modo que este não deve afetá-lo, a menos que você defina o buffer de a's correntes em a' s fonte.

Além disso, nãostdbuf é POSIX, mas parte do GNU-coreutils.

c00kiemon5ter
fonte
3
Obrigado, mas isso não parece estar disponível no OS X (a questão é osx-lion).
houbysoft
2
@houbysoft - Tenho certeza de que as ferramentas GNU podem ser instaladas no OS X
jordanm
1
@jordanm: talvez, mas instalar todas as ferramentas GNU parece um exagero para isso ...
houbysoft
1
Votei nesta resposta porque stdbufjá está disponível nas distribuições Centos Linux que estamos usando, e unbuffernão está. Obrigado!
Huw Walters
6
Para o script python, o stdbuf não funcionará, mas você pode usar -upara desativar o buffer no lado do python:python3 -u a.py | tee output.txt
Honza
27

Você também pode tentar executar seu comando em um pseudo-terminal usando o scriptcomando (que deve forçar a saída do buffer de linha para o pipe)!

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

Esteja ciente de que o scriptcomando não propaga de volta o status de saída do comando encapsulado.

Jon
fonte
3
script -t 1 /path/to/outputfile.txt ./afuncionou muito bem para o meu caso de uso. Ele transmite ao vivo toda a saída ao outputfile.txtmesmo tempo que a imprime no stdout do shell. Não precisava usartee
Peter Berg
26

Você pode usar setlinebuf de stdio.h.

setlinebuf(stdout);

Isso deve alterar o buffer para "buffer de linha".

Se precisar de mais flexibilidade, você pode usar setvbuf.

Denys Rtveliashvili
fonte
8
Eu me pergunto por que essa solução tem tão poucos votos positivos. Esta é a única solução que não sobrecarrega o chamador.
Oxigênio
1
Observe que este não é o C padrão (ou mesmo POSIX). Provavelmente é melhor usar setvbuf(stdout, NULL, _IOLBF, 0), o que é exatamente equivalente.
rvighne de
Isso corrigiu meu problema no OS X Catalina com um programa C ++ que estava imprimindo () e eu estava indo direto ao ponto, mas só estava vendo a saída quando o programa terminou.
jbaxter
2

Se você usar as classes de fluxo C ++ em vez disso, every std::endl será uma liberação implícita. Usando a impressão estilo C, acho que o método que você sugeriu ( fflush()) é a única maneira.

Kevin Grant
fonte
4
Infelizmente, isto não é verdade. Você pode observar o mesmo comportamento com c ++ std :: cout mesmo ao usar std :: endl ou std :: flush. O buffering acontece no topo e a solução mais fácil no Linux parece ser setlinebuf (stdout); como a primeira linha em main () quando você é o autor do programa e usa as outras soluções acima quando não consegue alterar o código-fonte.
Oxigênio
1
@oxygene Isso não é verdade. Eu tentei e o endl esvazia o buffer ao direcionar para o tee (ao contrário do printf). Código: #include <iostream> #include <unistd.h> int main(void) { std::cout << "1" << std::endl; sleep(1); std::cout << "2" << std::endl; }. endl sempre limpa o buffer conforme definido aqui: en.cppreference.com/w/cpp/io/manip/endl
Curtis Yallop