Desativar buffering no pipe

359

Eu tenho um script que chama dois comandos:

long_running_command | print_progress

O long_running_command imprime um progresso, mas não estou satisfeito com isso. Estou usando print_progress para torná-lo mais legal (ou seja, imprimo o progresso em uma única linha).

O problema: Conexão de um pipe para stdout também ativa um buffer de 4K, para o bom programa de impressão não fica nada ... nada ... nada ... muito ... :)

Como posso desativar o buffer 4K para o long_running_command (não, eu não tenho a fonte)?

    
por Aaron Digulla 16.06.2009 / 12:27

13 respostas

227

Você pode usar o comando expect unbuffer , por exemplo

unbuffer long_running_command | print_progress

unbuffer conecta-se a long_running_command através de um pseudoterminal (pty), o que faz com que o sistema o trate como um processo interativo, portanto não usando o buffer de 4-kiB no pipeline que é a causa provável do atraso. / p>

Para pipelines mais longos, você pode ter que unbuffer cada comando (exceto o final), por exemplo,

unbuffer x | unbuffer -p y | z
    
por 16.06.2009 / 13:03
421

Outra maneira de eliminar esse gato é usar o stdbuf programa, que faz parte do GNU Coreutils (o FreeBSD também tem o seu próprio).

stdbuf -i0 -o0 -e0 command

Isso desativa completamente o buffer para entrada, saída e erro. Para algumas aplicações, o buffer de linha pode ser mais adequado por motivos de desempenho:

stdbuf -oL -eL command

Observe que ele só funciona para stdio buffering ( printf() , fputs() ...) para aplicativos vinculados dinamicamente e somente se esse aplicativo não ajustar o buffer de seus fluxos padrão em si, embora isso deva cobrir a maioria das aplicações.

    
por 19.06.2011 / 09:12
64

No entanto, outra maneira de ativar o modo de saída de buffer de linha para o long_running_command é usar o comando script que executa seu long_running_command em um pseudo terminal (pty).

script -q /dev/null long_running_command | print_progress      # FreeBSD, Mac OS X
script -c "long_running_command" /dev/null | print_progress    # Linux
    
por 19.01.2013 / 14:05
58

Para grep , sed e awk , você pode forçar a saída a ser armazenada em buffer de linha. Você pode usar:

grep --line-buffered

Força a saída a ser armazenada em buffer de linha. Por padrão, a saída é armazenada em buffer de linha quando a saída padrão é um terminal e o bloco é armazenado em buffer.

sed -u

Tornar a linha de saída em buffer.

Veja esta página para mais informações: link

    
por 31.10.2012 / 17:00
47

Se for um problema com a libc modificar seu buffering / flushing quando a saída não for para um terminal, você deve tentar socat . Você pode criar um fluxo bidirecional entre quase qualquer tipo de mecanismo de E / S. Um deles é um programa bifurcado falando com um pseudo-tty.

 socat EXEC:long_running_command,pty,ctty STDIO 

O que isso faz é

  • crie um pseudo tty
  • fork long_running_command com o lado do escravo do pty como stdin / stdout
  • estabeleça um fluxo bidirecional entre o lado mestre do pty e o segundo endereço (aqui é STDIO)

Se isso lhe der a mesma saída que long_running_command , você poderá continuar com um pipe.

Editar: Uau Não viu a resposta unbuffer! Bem, socat é uma ótima ferramenta de qualquer maneira, então eu posso deixar esta resposta

    
por 17.06.2009 / 09:21
18

Você pode usar

long_running_command 1>&2 |& print_progress

O problema é que a libc armazenará o buffer de linha quando o stdout for exibido na tela e o buffer completo quando o stdout estiver em um arquivo. Mas sem buffer para stderr.

Eu não acho que seja o problema com buffer de pipe, é tudo sobre a política de buffer da libc.

    
por 04.04.2014 / 08:10
10

Esse costumava ser o caso, e provavelmente ainda é o caso, que quando a saída padrão é gravada em um terminal, ela é armazenada em buffer por padrão - quando uma nova linha é gravada, a linha é gravada no terminal. Quando a saída padrão é enviada para um pipe, ela é totalmente armazenada em buffer - portanto, os dados são enviados apenas para o próximo processo no pipeline quando o buffer de E / S padrão é preenchido.

Essa é a fonte do problema. Eu não tenho certeza se há muito o que você pode fazer para consertá-lo sem modificar o programa escrito no pipe. Você pode usar a função setvbuf() com o sinalizador _IOLBF para colocar incondicionalmente stdout no modo de linha em buffer. Mas não vejo uma maneira fácil de impor isso em um programa. Ou o programa pode fazer fflush() em pontos apropriados (após cada linha de saída), mas o mesmo comentário se aplica.

Suponho que, se você substituísse o pipe por um pseudo-terminal, a biblioteca de E / S padrão pensaria que a saída era um terminal (porque é um tipo de terminal) e que o buffer de linha seria automaticamente. Essa é uma maneira complexa de lidar com as coisas.

    
por 17.06.2009 / 02:47
6

Eu sei que essa é uma pergunta antiga e já tinha muitas respostas, mas se você quiser evitar o problema do buffer, tente algo como:

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

Isso produzirá os logs em tempo real e também os salvará no arquivo logs.txt e o buffer não afetará mais o comando tail -f .

    
por 07.08.2015 / 10:31
4

Eu não acho que o problema seja com o pipe. Parece que seu processo de longa execução não está liberando seu próprio buffer com freqüência suficiente. Alterar o tamanho do buffer do pipe seria um truque para contorná-lo, mas não acho que seja possível sem reconstruir o kernel - algo que você não gostaria de fazer como um hack, pois provavelmente afetaria muitos outros processos.

    
por 16.06.2009 / 12:45
2

Na mesma linha da resposta do chad , você pode escrever um pequeno roteiro como este:

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

Em seguida, use este comando scriptee como um substituto para tee .

my-long-running-command | scriptee

Infelizmente, parece que não consigo obter uma versão desse tipo para funcionar perfeitamente no Linux, por isso parece limitada a unis de estilo BSD.

No Linux, isso está perto, mas você não recebe o seu prompt quando termina (até que você pressione enter, etc) ...

script -q -c 'cat > /proc/self/fd/1' /dev/null
    
por 18.11.2016 / 02:12
1

De acordo com este post aqui , você poderia tentar reduzir o ulimit do tubo para um único bloco de 512 bytes. Certamente não irá desativar o buffer, mas bem, 512 bytes é bem menos que 4K: 3

    
por 20.05.2014 / 21:43
1

Eu encontrei esta solução inteligente: (echo -e "cmd 1\ncmd 2" && cat) | ./shell_executable

Isso faz o truque. cat lerá entrada adicional (até EOF) e passará para o canal depois que echo colocar seus argumentos no fluxo de entrada de shell_executable .

    
por 04.11.2016 / 12:01
-1

De acordo com este o tamanho do buffer de pipe parece estar no kernel e exigiria que você recompilasse seu kernel para alterar.

    
por 16.06.2009 / 12:41