grep não sai até EOF se for canalizado pelo gato

20

Dado este exemplo mínimo

( echo "LINE 1" ; sleep 1 ; echo "LINE 2" ; )

gera LINE 1 e, depois de um segundo, gera LINE 2 , conforme o esperado .

Se canalizarmos isso para grep LINE

( echo "LINE 1" ; sleep 1 ; echo "LINE 2" ; ) | grep LINE

o comportamento é o mesmo do caso anterior, conforme o esperado .

Se, alternativamente, enviarmos isso para cat

( echo "LINE 1" ; sleep 1 ; echo "LINE 2" ; ) | cat

o comportamento é novamente o mesmo, conforme o esperado .

No entanto , se enviarmos para grep LINE e, em seguida, para cat ,

( echo "LINE 1" ; sleep 1 ; echo "LINE 2" ; ) | grep LINE | cat

não há saída até passar um segundo, e ambas as linhas aparecem na saída imediatamente, o que não esperava .

Por que isso está acontecendo e como posso fazer a última versão se comportar da mesma maneira que os três primeiros comandos?

    
por lisyarus 05.09.2018 / 17:08

3 respostas

39

Quando (pelo menos GNU) a saída de grep não é um terminal, ela armazena em buffer sua saída, que é o que causa o comportamento que você está vendo. Você pode desativar isso usando a opção grep do GNU --line-buffered :

( echo "LINE 1" ; sleep 1 ; echo "LINE 2" ; ) | grep --line-buffered LINE | cat

ou o utilitário stdbuf :

( echo "LINE 1" ; sleep 1 ; echo "LINE 2" ; ) | stdbuf -oL grep LINE | cat

Desativar o buffer no tubo tem mais sobre esse assunto.

    
por 05.09.2018 / 17:12
26

Explicação simplificada

Como muitos utilitários, isso não é algo peculiar a um programa, grep varia sua saída padrão entre linha em buffer e totalmente em buffer . No primeiro caso, a biblioteca C armazena os dados de saída na memória até que o buffer contendo esses dados seja preenchido ou um caractere de alimentação de linha seja adicionado a ele (ou o programa termina de forma limpa), ao que chama write() para realmente gravar o conteúdo do buffer . No último caso, somente o buffer na memória se tornando cheio (ou o programa terminando de forma limpa) aciona o write() .

Explicação mais detalhada

Esta é a explicação bem conhecida, mas ligeiramente errada. Na verdade, a saída padrão não é armazenada em buffer de linha, mas smart buffered na biblioteca GNU C e na biblioteca BSD C. A saída padrão é também liberada quando a leitura da entrada padrão esgota seu buffer na memória (da entrada pré-leitura) e a biblioteca C precisa chamar read() para buscar mais alguma entrada e está lendo o começo de uma nova linha. (Um motivo para isso é impedir o deadlock quando outro programa se conecta a ambas as extremidades de um filtro e espera poder operar linha por linha, alternando entre gravar no filtro e ler a partir dele, como "coprocessos" no GNU awk por exemplo.

Influência da biblioteca C

grep e os outros utilitários fazem isso - ou, mais estritamente, as bibliotecas C que eles usam fazem isso, porque esse é um recurso definido de programação na linguagem C - baseado no que eles detectam sua saída padrão. . Se (e somente se) não for um dispositivo interativo, eles escolhem o buffer completo, caso contrário, eles escolhem o armazenamento em buffer inteligente. Um pipe é considerado não como um dispositivo interativo, porque a definição de ser um dispositivo interativo, pelo menos no mundo do Unix e do Linux, é essencialmente a chamada isatty() que retorna true para o descritor de arquivo relevante.

Soluções alternativas para desativar o buffer completo

Alguns utilitários como grep têm opções idiossincráticas, como --line-buffered , que alteram essa decisão, o que, como você pode ver, é mal-nomeado. Mas uma fração extremamente pequena dos programas de filtragem que se pode usar realmente tem essa opção.

Em geral, é possível usar ferramentas que acessam os recursos internos específicos da biblioteca C e alteram sua tomada de decisão (que tem problemas de segurança se o programa a ser alterado for set-UID e também específico para determinadas bibliotecas C, e, de fato, são específicos para programas escritos em camadas ou em cima da linguagem C), ou ferramentas como ptybandage que não alteram os componentes internos do programa, mas simplesmente interpõem um pseudo-terminal como padrão saída para que a decisão saia como "interativa", para afetar isso.

Leitura adicional

por 05.09.2018 / 17:51
7

Use

grep --line-buffered

para fazer o grep não armazenar mais de uma linha por vez.

    
por 05.09.2018 / 17:10

Tags