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)
Este comando é exibido 1234512345
conforme o esperado. Mas isso funciona apenas na implementação de coreutils do head
utilitário. A implementação do busybox ainda consome caracteres extras, portanto a saída é justa 12345
.
Eu acho que essa maneira específica de implementação é feita para fins de otimização. Você não pode saber onde a linha termina, portanto, não sabe quantos caracteres precisa ler. A única maneira de não consumir caracteres extras do fluxo de entrada é ler o fluxo byte a byte. Mas a leitura do fluxo, um byte de cada vez, pode ser lenta. Então, eu acho que head
lê o fluxo de entrada em um buffer grande o suficiente e depois conta as linhas nesse buffer.
O mesmo não pode ser dito para o caso em que a --bytes
opção é usada. Nesse caso, você sabe quantos bytes precisa ler. Então você pode ler exatamente esse número de bytes e não mais do que isso. A implementação do corelibs usa essa oportunidade, mas a do busybox não, ela ainda lê mais bytes do que o necessário em um buffer. Provavelmente isso é 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ê precisa pressionar Ctrl+C
para interromper os comandos acima. Os utilitários Unix não falham na leitura além 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 não usei por simplicidade.
fonte
Respostas:
Sim, é permitido (veja abaixo).
Sim, POSIX volume 3, Shell e Utilitários .
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.GNU
head
não tentar deixar o descritor de arquivo na posição correta, mas é impossível para buscar em tubos, por isso, o teste não consegue restaurar a posição. Você pode ver isso usandostrace
:O
read
retorna 17 bytes (toda a entrada disponível),head
processa quatro deles e tenta retornar 13 bytes, mas não pode. (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, para que possa (se implementado dessa maneira) limitar sua leitura adequadamente. É por isso que seuhead -c 5
teste funciona: o GNUhead
lê apenas cinco bytes e, portanto, não precisa restaurar a posição do descritor de arquivo.Se você gravar o documento em um arquivo e usá-lo, obterá o comportamento que deseja:
fonte
line
(agora removidos do POSIX / XPG, mas ainda disponíveis em muitos sistemas) ouread
(IFS= read -r line
) que lêem um byte por vez para evitar o problema.head -c 5
a leitura de 5 bytes ou um buffer completo depende da implementação (observe também que issohead -c
não é padrão), você não pode confiar nisso. Você precisariadd bs=1 count=5
ter uma garantia de que não mais que 5 bytes serão lidos.-c 5
descrição.head
built-inksh93
lê um byte de cada vez,head -n 1
quando a entrada não é procurável.dd
só funciona corretamente com pipes,bs=1
se você usar umcount
como leituras em pipes pode retornar menos do que o solicitado (mas pelo menos um byte, a menos que eof seja alcançado). O GNUdd
temiflag=fullblock
que pode aliviar isso.POSIX
Não diz nada sobre o quanto
head
deve ler a partir da entrada. Exigir que ele leia byte a byte seria tolo, pois seria extremamente lento na maioria dos casos.No entanto, isso é abordado no
read
utilitário / embutido: todas as conchas que posso encontrarread
dos pipes um byte de cada 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 como isto:Aqui, a entrada padrão de
someprogram
é a mesma do shell, mas pode-se esperar quesomeprogram
consiga ler tudo o que vem após a primeira linha de entrada consumida peloread
e não o que resta depois de uma leitura em bufferread
. 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
fonte
-r
,read
pode ler mais de uma linha (semIFS=
ela também removeria espaços e guias à esquerda e à direita (com o valor padrão de$IFS
)).head
built-inksh93
lê um byte de cada vez,head -n 1
quando a entrada não é procurável.fonte