Esperava-se que o seguinte comando shell imprimisse apenas linhas ímpares do fluxo de entrada:
echo -e "aaa\nbbb\nccc\nddd\n" | (while true; do head -n 1; head -n 1 >/dev/null; done)
Mas, em vez disso, apenas imprime a primeira linha: aaa
.
O mesmo não acontece quando é usado com a opção -c
( --bytes
):
echo 12345678901234567890 | (while true; do head -c 5; head -c 5 >/dev/null; done)
Esse comando é gerado 1234512345
conforme o esperado. Mas isso funciona apenas na implementação coreutilshead
do utilitário. A implementação do busybox ainda consome caracteres extras, então a saída é apenas 12345
.
Acho que essa forma específica de implementação é feita para fins de otimização. Você não pode saber onde a linha termina, então não sabe quantos caracteres precisa ler. A única maneira de não consumir caracteres extras do fluxo de entrada é ler o fluxo byte por byte. Mas a leitura do fluxo um byte por vez pode ser lenta. Então, acho que head
lê o fluxo de entrada em um buffer grande o suficiente e, em seguida, conta as linhas nesse buffer.
O mesmo não pode ser dito para o caso em que a --bytes
opção é usada. Neste caso, você sabe quantos bytes precisa ler. Portanto, você pode ler exatamente esse número de bytes e não mais do que isso. A implementação corelibs usa essa oportunidade, mas a busybox não, ela ainda lê mais bytes do que o necessário em um buffer. Provavelmente é feito para simplificar a implementação.
Então a pergunta. É correto que o head
utilitário consuma mais caracteres do fluxo de entrada do que o solicitado? Existe algum tipo de padrão para utilitários Unix? E se houver, ele especifica esse comportamento?
PS
Você tem que pressionar Ctrl+C
para parar os comandos acima. Os utilitários Unix não falham na leitura além de EOF
. Se você não quiser pressionar, pode usar um comando mais complexo:
echo 12345678901234567890 | (while true; do head -c 5; head -c 5 | [ `wc -c` -eq 0 ] && break >/dev/null; done)
que eu não usei para simplificar.
Sim, é permitido (veja abaixo).
Sim, POSIX volume 3, Shell & Utilities .
Ele faz, em sua introdução:
head
é um dos utilitários padrão , portanto, uma implementação em conformidade com POSIX deve implementar o comportamento descrito acima.O GNU tenta deixar
head
o descritor de arquivo na posição correta, mas é impossível buscar em pipes, portanto, em seu teste, ele falha em restaurar a posição. Você pode ver isso usandostrace
:O
read
retorna 17 bytes (todas as entradas disponíveis),head
processa quatro deles e tenta retroceder 13 bytes, mas não consegue. (Você também pode ver aqui que o GNUhead
usa um buffer de 8 KiB.)Quando você diz
head
para contar bytes (o que não é padrão), ele sabe quantos bytes ler, então pode (se implementado dessa forma) limitar sua leitura de acordo. É por isso que seuhead -c 5
teste funciona: o GNUhead
lê apenas cinco bytes e, portanto, não precisa procurar restaurar a posição do descritor de arquivo.Se você gravar o documento em um arquivo e usá-lo, obterá o comportamento desejado:
de POSIX
Não diz nada sobre quanto
head
deve ler da entrada. Exigir que ele leia byte por byte seria bobagem, pois seria extremamente lento na maioria dos casos.Isso é, no entanto, abordado no
read
builtin/utility: todos os shells que posso encontrarread
de pipes um byte por vez e o texto padrão pode ser interpretado como significando que isso deve ser feito, para poder ler apenas uma única linha:No caso de
read
, que é usado em scripts de shell, um caso de uso comum seria algo assim:Aqui, a entrada padrão de
someprogram
é a mesma do shell, mas pode-se esperar quesomeprogram
leia tudo o que vem após a primeira linha de entrada consumida peloread
e não o que sobrou após uma leitura em buffer deread
. Por outro lado, usarhead
como no seu exemplo é muito mais incomum.Se você realmente deseja excluir todas as outras linhas, seria melhor (e mais rápido) usar alguma ferramenta que possa lidar com toda a entrada de uma só vez, por exemplo