Pipes, como os dados fluem em um pipeline?

22

Eu não entendo como os dados fluem no pipeline e espero que alguém possa esclarecer o que está acontecendo lá.

Eu pensei que um pipeline de comandos processa arquivos (texto, arrays de strings) de maneira linear. (Se cada comando funciona linha por linha). Cada linha de texto passa pelo pipeline, os comandos não esperam que o anterior termine o processamento da entrada inteira.

Mas parece que não é assim.

Aqui está um exemplo de teste. Existem algumas linhas de texto. Eu maiúsculas e repito cada linha duas vezes. Eu faço isso com cat text | tr '[:lower:]' '[:upper:]' | sed 'p' .

Para acompanhar o processo, podemos executá-lo "interativamente" - pule o nome do arquivo de entrada em cat . Cada parte do pipeline é executada linha por linha:

$ cat | tr '[:lower:]' '[:upper:]'
alkjsd
ALKJSD
sdkj
SDKJ
$ cat | sed 'p'
line1
line1
line1
line 2
line 2
line 2

Mas o pipeline completo espera que eu termine a entrada com EOF e só então imprime o resultado:

$ cat | tr '[:lower:]' '[:upper:]' | sed 'p'
I am writing...
keep writing...
now ctrl-D
I AM WRITING...
I AM WRITING...
KEEP WRITING...
KEEP WRITING...
NOW CTRL-D
NOW CTRL-D

É suposto ser assim? Por que não é linha por linha?

    
por xealits 31.01.2015 / 22:21

2 respostas

36

Existe uma regra de buffer geral seguida pela biblioteca de I / O padrão C ( stdio ) que a maioria dos programas unix usa. Se a saída estiver indo para um terminal, ela será liberada no final de cada linha; caso contrário, ele será liberado apenas quando o buffer (8K no meu sistema Linux / amd64; puder ser diferente no seu) estiver cheio.

Se todos os seus utilitários estivessem seguindo a regra geral, você veria a saída atrasada em todos os seus exemplos ( cat|sed , cat|tr e cat|tr|sed ). Mas há uma exceção: GNU cat nunca armazena sua saída. Ele não usa stdio ou altera a política de buffer stdio padrão.

Posso ter certeza de que você está usando o GNU cat e não algum outro unix cat porque os outros não se comportariam dessa maneira. O unix tradicional cat tem uma opção -u para solicitar saída sem buffer. O GNU cat ignora a opção -u porque sua saída está sempre sem buffer.

Portanto, sempre que você tiver um canal com cat à esquerda, no sistema GNU, a passagem de dados pelo canal não será atrasada. O cat não está nem seguindo linha por linha - seu terminal está fazendo isso. Enquanto você digita entrada para cat, seu terminal está no modo "canônico" - baseado em linha, com teclas de edição como backspace e ctrl-U oferecendo a chance de editar a linha que você digitou antes de enviá-la com Enter .

No cat|tr|sed example, tr ainda está recebendo dados de cat assim que você pressionar Enter , mas tr está seguindo a política padrão stdio : sua saída está indo para um pipe, então ele não flush após cada linha. Ele grava no segundo pipe quando o buffer está cheio ou quando um EOF é recebido, o que ocorrer primeiro.

sed também está seguindo a política padrão stdio , mas sua saída está indo para um terminal, portanto, ele gravará cada linha assim que tiver terminado. Isso tem um efeito sobre o quanto você deve digitar antes que algo apareça na outra extremidade do pipeline - se sed estivesse armazenando em buffer o seu resultado, você teria que digitar duas vezes mais (para preencher tr 's buffer de saída e sed do buffer de saída).

A opção

GNU sed tem -u , por isso, se você inverteu a ordem e usou cat|sed -u|tr , verá a saída aparecer instantaneamente novamente. (A opção sed -u pode estar disponível em outro lugar, mas eu não acho que seja uma tradição antiga do unix como cat -u ). Até onde eu sei, não existe uma opção equivalente para tr .

Existe um utilitário chamado stdbuf que permite alterar o modo de armazenamento em buffer de qualquer comando que use os stdio defaults. É um pouco frágil, pois usa LD_PRELOAD para realizar algo que a biblioteca C não foi projetada para suportar, mas, neste caso, parece funcionar:

cat | stdbuf -o 0 tr '[:lower:]' '[:upper:]' | sed 'p'
    
por 01.02.2015 / 00:03
8

Isso realmente me levou a pensar e a responder mais. Ótima pergunta (vou revê-lo em seguida).

Você deixou de experimentar tr | sed em seus itens de depuração acima:

>tr '[:lower:]' '[:upper:]' | sed 'p'
i am writing
still writing
now ctrl-d
I AM WRITING
I AM WRITING
STILL WRITING
STILL WRITING
NOW CTRL-D
NOW CTRL-D
>

Então, evidentemente, tr buffers. Aprenda algo novo todos os dias!

EDITAR :

Quando penso nisso, isolamos a causa, mas não fornecemos uma explicação. Se você cat | tr , escreve imediatamente, se você cat | sed , escreve imediatamente, mas se você tr | sed , aguarda por EOF . Eu sugeriria que a resposta poderia estar enterrada no código-fonte tr ou sed , e não ser um problema de cachimbo.

EDITAR :

Eu vejo o Wumpus fornecendo a explicação enquanto escrevia a última edição. Obrigado!

    
por 01.02.2015 / 00:27