Pipes, como os dados fluem em um pipeline?

22

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, matrizes de strings) linha por linha. (Se cada comando funcionar, linha por linha.) Cada linha de texto passa pelo pipeline, os comandos não esperam que o anterior termine o processamento de toda a entrada.

Mas parece que não é assim.

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

Para seguir o processo, podemos executá-lo "interativamente" - pule o nome do arquivo de entrada 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 EOFe só depois imprima 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

Deveria ser assim? Por que não é linha por linha?

xealits
fonte
Não é o cano, é catbuffer até o stdin fechar.
Goldilocks
mas tre sedfazer linhas de processo de catantes fecha stdin
xealits
Os padrões usados ​​pelo stdio (que eu acredito que todos os programas mencionados usam) é que stderr é sem buffer e stdout é buffer de linha ao gravar em um terminal e totalmente armazenado em buffer (por exemplo, se estiver gravando em um arquivo ou canal) . Alguns dos comandos têm sinalizadores que podem alterar o buffer stdout, mas parece que tr não.
kasperd

Respostas:

36

Existe uma regra geral de armazenamento em buffer seguida pela biblioteca de E / S padrão C ( stdio) usada pela maioria dos programas unix. 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; pode ser diferente no seu) estiver cheio.

Se todas as suas utilidades estavam seguindo a regra geral, você veria saída com atraso em todos os seus exemplos ( cat|sed, cat|tr, e cat|tr|sed). Mas há uma exceção: o GNU catnunca armazena em buffer sua saída. Ele não usa stdioou altera a stdiopolítica de buffer padrão .

Posso ter certeza de que você está usando o GNU cate não algum outro unix catporque os outros não se comportariam dessa maneira. O unix tradicional cattem uma -uopção para solicitar saída sem buffer. O GNU catignora a -uopção porque sua saída é sempre sem buffer.

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

No cat|tr|sedexemplo, trainda está recebendo dados catassim que você pressiona Enter, mas trsegue a stdiopolítica padrão: sua saída está indo para um canal, para que não seja liberada após cada linha. Ele grava no segundo canal quando o buffer está cheio ou quando um EOF é recebido, o que ocorrer primeiro.

sedtambém segue a stdiopolítica padrão, mas sua saída está indo para um terminal, para que ele escreva cada linha assim que terminar. Isso afeta o quanto você deve digitar antes que algo apareça na outra extremidade do pipeline - se o sedbuffer estivesse bloqueando sua saída, você teria que digitar o dobro (para preencher tro buffer de saída e sed a saída) amortecedor).

O GNU sedtem -uopção, portanto, se você reverter a ordem e usá- cat|sed -u|trlo, verá a saída aparecer instantaneamente novamente. (A sed -uopção pode estar disponível em outro lugar, mas eu não acho que seja uma tradição antiga do unix cat -u). Até onde eu sei, não há uma opção equivalente tr.

Existe um utilitário chamado stdbufque permite alterar o modo de buffer de qualquer comando que use os stdiopadrões. É um pouco frágil, pois é usado LD_PRELOADpara realizar algo que a biblioteca C não foi projetada para oferecer suporte, mas, neste caso, parece funcionar:

cat | stdbuf -o 0 tr '[:lower:]' '[:upper:]' | sed 'p'

fonte
1
obrigado! Resposta incrível. Provavelmente, devo mencionar o buffer na pergunta de alguma maneira, para que possamos encontrá-lo.
Xealits
teee ddtambém geralmente jogam de acordo com suas próprias regras. Quando combinadas de forma criativa, as três ferramentas podem negar de maneira portável qualquer necessidade de stdbufpipelines em segundo plano.
Mikeerv # 01/02
1
Esta é uma das razões para evitar o uso inútil do gato .
Hbbs
8

Isso realmente me levou a pensar um pouco para entender e ainda mais para responder. Ótima pergunta (votarei em seguida).

Você esqueceu de tentar tr | sednos 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, trbuffers. Aprenda algo novo todos os dias!

EDIT :

Enquanto penso sobre isso, isolamos a causa, mas não fornecemos uma explicação. Se você cat | tr, ele escreve imediatamente, se você cat | sed, ele escreve imediatamente, mas se você tr | sed, ele espera para EOF. Gostaria de sugerir a resposta pode ser enterrado em trou sedcódigo-fonte, em seguida, e não ser um problema de tubulação.

EDIT :

Vejo Wumpus fornecer a explicação enquanto eu estava digitando a última edição. Obrigado!

Poisson Aerohead
fonte
1
na verdade, eles amortecem! e o teste com linhas de aproximadamente 8kb, como Wumpus mencionou, mostra que o buffer é de fato 8Kb. Gostaria de aceitar as duas respostas para compartilhar alguma reputação, mas considerarei a de Wumpus a mais completa. Obrigado mesmo assim!
Xlits1
1
Sem problemas, a minha era a resposta empírica, a dele era a que conhecia.
Poisson Aerohead
Veja também esta pergunta que mostra como usar o stdbufque também pode ser útil. unix.stackexchange.com/questions/182537/…
Joe