Eles se entrelaçam! Você tentou apenas rajadas curtas de saída, que permanecem sem divisão, mas na prática é difícil garantir que uma saída específica permaneça sem divisão.
Buffer de saída
Depende de como os programas armazenam sua saída em buffer . A biblioteca stdio que a maioria dos programas usa ao escrever usa buffers para tornar a saída mais eficiente. Em vez de emitir dados assim que o programa chama uma função de biblioteca para gravar em um arquivo, a função armazena esses dados em um buffer e, na verdade, somente os dados são emitidos após o preenchimento do buffer. Isso significa que a saída é feita em lotes. Mais precisamente, existem três modos de saída:
- Sem buffer: os dados são gravados imediatamente, sem usar um buffer. Isso pode ser lento se o programa gravar sua saída em pedaços pequenos, por exemplo, caractere por caractere. Este é o modo padrão para erro padrão.
- Totalmente em buffer: os dados são gravados apenas quando o buffer está cheio. Este é o modo padrão ao gravar em um canal ou em um arquivo regular, exceto com stderr.
- Buffer de linha: os dados são gravados após cada nova linha ou quando o buffer está cheio. Este é o modo padrão ao gravar em um terminal, exceto com stderr.
Os programas podem reprogramar cada arquivo para se comportar de maneira diferente e podem liberar explicitamente o buffer. O buffer é liberado automaticamente quando um programa fecha o arquivo ou sai normalmente.
Se todos os programas que estão gravando no mesmo canal usam o modo de buffer de linha ou usam o modo sem buffer e gravam cada linha com uma única chamada para uma função de saída e se as linhas são curtas o suficiente para gravar em um único pedaço, a saída será uma intercalação de linhas inteiras. Mas se um dos programas usar o modo totalmente em buffer ou se as linhas forem muito longas, você verá linhas mistas.
Aqui está um exemplo em que intercalo a saída de dois programas. Eu usei o GNU coreutils no Linux; versões diferentes desses utilitários podem se comportar de maneira diferente.
yes aaaa
escreve aaaa
para sempre no que é essencialmente equivalente ao modo de buffer de linha. Na yes
verdade, o utilitário grava várias linhas por vez, mas cada vez que emite saída, a saída é um número inteiro de linhas.
echo bbbb; done | grep b
grava bbbb
para sempre no modo com buffer total. Ele usa um tamanho de buffer de 8192 e cada linha tem 5 bytes de comprimento. Como 5 não divide 8192, os limites entre gravações não estão em um limite de linha em geral.
Vamos lançá-los juntos.
$ { yes aaaa & while true; do echo bbbb; done | grep b & } | head -n 999999 | grep -e ab -e ba
bbaaaa
bbbbaaaa
baaaa
bbbaaaa
bbaaaa
bbbaaaa
ab
bbbbaaa
Como você pode ver, sim, algumas vezes interrompia o grep e vice-versa. Apenas cerca de 0,001% das linhas foram interrompidas, mas aconteceu. A saída é aleatória para que o número de interrupções varie, mas eu vi pelo menos algumas interrupções todas as vezes. Haveria uma fração mais alta de linhas interrompidas se as linhas fossem mais longas, pois a probabilidade de uma interrupção aumenta à medida que o número de linhas por buffer diminui.
Existem várias maneiras de ajustar o buffer de saída . Os principais são:
- Desative o buffer em programas que usam a biblioteca stdio sem alterar suas configurações padrão com o programa
stdbuf -o0
encontrado no GNU coreutils e em alguns outros sistemas como o FreeBSD. Como alternativa, você pode alternar para o buffer de linha com stdbuf -oL
.
- Alterne para o buffer de linha direcionando a saída do programa através de um terminal criado apenas para esta finalidade com
unbuffer
. Alguns programas podem se comportar de maneira diferente de outras maneiras, por exemplo, grep
usa cores por padrão se sua saída for um terminal.
- Configure o programa, por exemplo, passando
--line-buffered
para o GNU grep.
Vamos ver o trecho acima novamente, desta vez com buffer de linha nos dois lados.
{ stdbuf -oL yes aaaa & while true; do echo bbbb; done | grep --line-buffered b & } | head -n 999999 | grep -e ab -e ba
abbbb
abbbb
abbbb
abbbb
abbbb
abbbb
abbbb
abbbb
abbbb
abbbb
abbbb
abbbb
abbbb
Portanto, desta vez, o yes nunca interrompeu o grep, mas o grep às vezes interrompeu o yes. Eu irei ao porquê mais tarde.
Intercalação de tubos
Desde que cada programa produza uma linha de cada vez, e as linhas sejam curtas o suficiente, as linhas de saída serão bem separadas. Mas há um limite para quanto tempo as linhas podem demorar para que isso funcione. O próprio tubo possui um buffer de transferência. Quando um programa gera um canal, os dados são copiados do programa gravador para o buffer de transferência do canal e, posteriormente, do buffer de transferência do canal para o programa leitor. (Pelo menos conceitualmente - o kernel às vezes pode otimizar isso para uma única cópia.)
Se houver mais dados para copiar do que cabe no buffer de transferência do pipe, o kernel copia um buffer de cada vez. Se vários programas estão gravando no mesmo canal, e o primeiro programa escolhido pelo kernel deseja gravar mais de um buffer, não há garantia de que o kernel escolherá o mesmo programa novamente na segunda vez. Por exemplo, se P é o tamanho do buffer, foo
deseja escrever 2 * P bytes e bar
deseja escrever 3 bytes, uma intercalação possível é P bytes de foo
, depois 3 bytes de bar
e P bytes de foo
.
Voltando ao exemplo yes + grep acima, no meu sistema, yes aaaa
acontece o número de linhas que cabem em um buffer de 8192 bytes de uma só vez. Como existem 5 bytes para escrever (4 caracteres imprimíveis e a nova linha), isso significa que ele grava 8190 bytes sempre. O tamanho do buffer do canal é 4096 bytes. Portanto, é possível obter 4096 bytes de yes, alguma saída do grep e o restante da gravação de yes (8190 - 4096 = 4094 bytes). 4096 bytes deixam espaço para 819 linhas com aaaa
e um solitário a
. Daí uma linha com este solitário, a
seguida por uma gravação de grep, fornecendo uma linha com abbbb
.
Se você quiser ver os detalhes do que está acontecendo, getconf PIPE_BUF .
informará o tamanho do buffer do canal no seu sistema e poderá ver uma lista completa das chamadas do sistema feitas por cada programa com
strace -s9999 -f -o line_buffered.strace sh -c '{ stdbuf -oL yes aaaa & while true; do echo bbbb; done | grep --line-buffered b & }' | head -n 999999 | grep -e ab -e ba
Como garantir a intercalação de linhas limpas
Se os comprimentos da linha forem menores que o tamanho do buffer do tubo, o buffer da linha garante que não haverá nenhuma linha mista na saída.
Se os comprimentos das linhas puderem ser maiores, não há como evitar a mistura arbitrária quando vários programas estiverem gravando no mesmo canal. Para garantir a separação, você precisa fazer com que cada programa grave em um canal diferente e use um programa para combinar as linhas. Por exemplo, o GNU Parallel faz isso por padrão.