grep não gera saída até o EOF se for canalizado através do gato

19

Dado este exemplo mínimo

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

ele gera LINE 1e, depois de um segundo, saídas LINE 2, como 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, canalizarmos isso para cat

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

o comportamento é novamente o mesmo, como esperado .


No entanto , se canalizarmos para grep LINE, e depois para cat,

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

não há saída até que um segundo passe e as duas linhas apareçam na saída imediatamente, o que eu não esperava .


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

lisyarus
fonte
catconcatena arquivos. O que você está tentando fazer canalizando cat?
Douglas Held
15
@DouglasHeld Quando chamado sem argumentos, catsimplesmente lê stdine envia para stdout. Claro, eu vim com essa pergunta com um monte de coisas complexas no lugar deecho e cat, mas elas se mostraram irrelevantes, pois o problema aparece com exemplos muito mais simples.
Lisharus # 5/18
3
@DouglasHeld: canalizar para o gato geralmente é útil para forçar o stdout a não ser um terminal. Por exemplo, essa é uma maneira fácil de obter muitos comandos para não usar saída colorida.
wchargin
Juro que essa é uma duplicata de outra pergunta no Stack Overflow!
iBug 7/09/18
@wchargin muito obrigado, você me ensinou algo novo sobre o posix que eu nunca soube.
Douglas Held

Respostas:

38

Quando a grepsaída (pelo menos GNU) 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 grepa --line-bufferedopção do GNU :

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

ou o stdbufutilitário:

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

Desativar o buffer no tubo tem mais sobre este tópico.

Stephen Kitt
fonte
26

Explicação simplificada

Como muitos utilitários, isso não é algo peculiar a um programa, grepvaria sua saída padrão entre o buffer de linha e o buffer total . No primeiro caso, os buffers da biblioteca C emitem dados na memória até que o buffer que contém esses dados seja preenchido ou um caractere de avanço de linha seja adicionado a ele (ou o programa termine corretamente), após o que ele chama write()para realmente gravar o conteúdo do buffer. Neste último caso, apenas o buffer da memória que fica cheio (ou o programa que termina corretamente) aciona o write().

Explicação mais detalhada

Essa é a explicação bem conhecida, mas um pouco errada. De fato, a saída padrão não é de buffer de linha, mas de buffer inteligente nas bibliotecas GNU C e BSD C. A saída padrão também é liberada quando a leitura da entrada padrão esgota seu buffer de memória (da entrada pré-leitura) e a biblioteca C precisa chamar read()para buscar mais alguma entrada e está lendo o início de uma nova linha. (Uma razão para isso é evitar conflito 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 "coprocesses" no GNUawk por exemplo.)

Influência da biblioteca C

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

Soluções alternativas para desativar o buffer completo

Alguns utilitários, como grepopções idiossincráticas, como--line-buffered essa, alteram essa decisão, que, como você pode ver, tem um nome errado. Mas uma fração muito pequena dos programas de filtro que se pode usar realmente tem essa opção.

De maneira mais geral, pode-se usar ferramentas que vasculham as partes internas específicas da biblioteca C e alteram sua tomada de decisão (que têm problemas de segurança se o programa a ser alterado for UID definido, além de serem específicas de bibliotecas C específicas e, de fato, serem específicos para programas escritos ou em camadas sobre a linguagem C), ou ferramentas como essas ptybandageque não alteram as partes internas do programa, mas simplesmente interpõem um pseudo-terminal como saída padrão, para que a decisão saia como "interativa", para afetar isso.

Leitura adicional

JdeBP
fonte
11
Se a frase "linha em buffer" é um nome impróprio, não é realmente culpa de grep, mas das chamadas de biblioteca subjacentes, setbuf/setvbuf . Não conheço uma referência on-line confiável para o padrão C, mas, por exemplo, as páginas de manual do Linux e FreeBSD, juntamente com a descrição do POSIX, setvbufchamam "line buffered". Até a constante simbólica é _IOLBF.
Ilkkachu
Bem, agora você aprendeu melhor. Essa estratégia de buffer é descrita na documentação da biblioteca GNU C, embora brevemente. Laurent Bercot é mais direto sobre o assunto. Eu também mencionei isso.
JdeBP # 6/18
Eu não pensei que "Sua expectativa está errada" foi um bom título para esta excelente explicação sobre o buffer de saída. Espero que você não se importe de removê-lo e adicionar alguns cabeçalhos descritivos para cada seção da resposta.
Anthony G - justice para Monica
2
@ilkkachu O padrão C realmente usa "line buffered". De acordo com 7.21.3 Arquivos , parágrafo 3 : "Quando um fluxo é armazenado em buffer, ... Quando um fluxo é totalmente armazenado em buffer, ... Quando um fluxo é armazenado em buffer, os caracteres devem ser transmitidos para ou a partir do ambiente host como um bloquear quando um caractere de nova linha for encontrado. ... "De fato, o C Standard usa a frase exata" line buffered "cinco vezes. Portanto, não é um nome impróprio.
Andrew Henle
11
Além disso, a abordagem descrita aqui como "buffer inteligente", como eu a entendo, parece ser exatamente o que o padrão C descreve como "buffer de linha". Especificamente, além de liberar o buffer em novas linhas, "Quando um fluxo é armazenado em buffer de linha, os caracteres devem ser transmitidos para ou do ambiente host como um bloco quando a entrada [...] é solicitada em um fluxo sem buffer ou quando a entrada é solicitada em um fluxo de linha com buffer que requer a transmissão de caracteres do ambiente host ". Portanto, isso não é uma peculiaridade do GNU ou BSD, mas o que a linguagem exige.
John Bollinger
7

Usar

grep --line-buffered

para fazer com que o grep não armazene em buffer mais de uma linha por vez.

choroba
fonte