Observação: se alguma informação estiver incorreta, por favor, comente para que eu possa corrigir ou excluir.
Como @mosvy e @MichaelHomer mencionados nos comentários, isso se deve ao agendador agendar cada lado do canal de maneira diferente e em momentos diferentes. Para ser claro, estamos respondendo por que o seguinte tem resultados inconsistentes:
{ for ((i = 0; i < 1000; ++i)); do echo $i; done } | { head -n 1; echo ...; tail -n 1; }
com saída como:
0
...
e:
0
...
999
Dois pontos-chave estão em jogo aqui. A resposta curta é que, como a entrada no lado direito do canal nem sempre está disponível de uma só vez (ponto 1), head
"consumirá" quantidades diferentes. Se toda a entrada estiver disponível (o que significa que o lado esquerdo foi concluído primeiro), toda a entrada será consumida devido à implementação de head
, conforme explicado por @Kusalananda e @mosvy (ponto 2).
Primeiro mostraremos o ponto 1. A maneira mais fácil de mostrar isso é substituir tail
por head
:
$ ~ { for ((i = 0; i < 1000; ++i)); do echo $i; done } | { head -n 1; echo ...; head -n 1; }
0
...
878
$ ~ { for ((i = 0; i < 1000; ++i)); do echo $i; done } | { head -n 1; echo ...; head -n 1; }
0
...
820
$ ~ { for ((i = 0; i < 1000; ++i)); do echo $i; done } | { head -n 1; echo ...; head -n 1; }
0
...
$ ~ { for ((i = 0; i < 1000; ++i)); do echo $i; done } | { head -n 1; echo ...; head -n 1; }
0
...
796
Como podemos ver, a saída do segundo head
é diferente a cada vez. Isso mostra que a entrada do lado esquerdo nem sempre está disponível de uma só vez (ponto 1).
Para cada caso em que há um número após ...
, obteremos uma saída de 999
se usarmos tail
. Para o caso em que nada veio depois de ...
, veremos o mesmo para tail
. Para provar isso, mostraremos o ponto 2.
Embora não haja nada que possamos realmente fazer sobre o ponto 1, nós podemos torná-lo mais estável escrevendo-o em um arquivo:
$ ~ { for ((i = 0; i < 1000; ++i)); do echo $i; done } >input
Com o arquivo, vamos lê-lo através de um pipe (veja abaixo caso de redirecionamento):
$ ~ cat input | { head -n 1; echo ...; tail -n 1; }
0
...
E, de fato, head
consome tudo, não deixando nada para tail
. Como tal, temos o ponto 2. Assim, com o ponto 1 e o ponto 2, podemos explicar o comportamento inconsistente:
In my version of head
, at least 1000 lines will be consumed at a time if read through a pipe, and at least 1000 lines are available (the whole thing if less). If all of the left side finishes before the right side even starts, head
will consume everything, leaving nothing for tail
. If, however, the left side does not finish, head
will only consume those that are done. This means something is leftover for tail
, thus leaving an output.
Redirecionamento
Assim, no exemplo acima, usamos um pipe para fornecer o resultado. O raciocínio é que, se usássemos o redirecionamento, acabaríamos com o seguinte resultado:
$ ~ { head -n 1; echo ...; tail -n 1; } <input
0
...
999
O que é diferente da explicação acima. O raciocínio é que, quando usado dessa forma, parece que head
apenas lê uma linha:
$ ~ { head -n 1; echo ...; head -n 1; } <input
0
...
1
A maneira de explicar isso é fazer referência à resposta aqui . Resumindo:
- pipes are not lseek()'able so commands can't read some data and then rewind back, but when you redirect with > or < usually it's a file which is lseek() able object, so commands can navigate however they please.
Em outras palavras, head
não precisa consumir tudo se for capaz de buscar o arquivo diretamente. Só precisa ler o quanto for necessário. Depois de encontrar uma nova linha, pode colocar tudo de volta. Podemos provar isso usando um arquivo com 1 byte após uma nova linha:
$ ~ cat input
0123456789
1
$ ~ { head -n 1; head -c 1; } <input
0123456789
1$ ~
Se usarmos um pipe, toda a entrada será consumida, sem nada para o segundo head
:
$ ~ cat input | { head -n 1; head -c 1; }
0123456789
$ ~
Como uma nota secundária, se usássemos a substituição de processo (o que resulta em uma leitura não-pesquisável como eu a entendo), obteremos o mesmo resultado:
$ ~ { head -n 1; head -c 1; } < <(cat input)
0123456789
$ ~