Este comportamento de cauda está nos comandos de agrupamento especificados por POSIX?

7

Usar o tail combinado com outras ferramentas padrão nos Comandos de agrupamento pode fazer construções poderosas. Por exemplo, para obter a primeira e última linha de um arquivo:

$ seq 10 > file
$ { head -n1; tail -n1; } <file
1
10

Ao alimentar o conteúdo do arquivo de um pipe para agrupar comandos, tail não produz saída, porque um canal é un - lseek capaz :

$ seq 10 | { head -n1; tail -n1; }
1

Agora, quando o conteúdo é grande o suficiente, tail funciona:

$ seq 10000 | { head -n1; tail -n1; }
1
10000

Isso porque após a primeira falha de lseek , tail sabe que não é um descritor de arquivo lseekable e porque o conteúdo do pipe ainda não foi lido, ele começa a ler o conteúdo até o fim.

Como ponto de vista do usuário, espero que o comportamento seja consistente, independentemente do tamanho do conteúdo de entrada. Eu olhei pela documentação do POSIX tail , lseek e não descobri nenhuma descrição.

Este comportamento é especificado por POSIX? Se não, como posso fazer com que o resultado seja sempre consistente?

Eu testei com o final do GNU e a cauda do FreeBSD, ambos têm o mesmo comportamento.

    
por cuonglm 29.10.2015 / 18:18

1 resposta

7

Note que o problema não é com tail , mas com head aqui, que lê a partir do pipe mais do que a primeira linha a ser produzida (então não sobra nada para tail ).

E sim, é compatível com POSIX.

head é necessário para deixar o cursor dentro de stdin logo após a última linha que ele gerou quando a entrada é procurada, mas não o contrário.

link :

When a standard utility reads a seekable input file and terminates without an error before it reaches end-of-file, the utility shall ensure that the file offset in the open file description is properly positioned just past the last byte processed by the utility. For files that are not seekable, the state of the file offset in the open file description for that file is unspecified.

Por head poder fazer isso para um arquivo não-pesquisável, isso significaria que ele teria que ler um byte de cada vez, o que seria terrivelmente ineficiente¹. Isso é o que o utilitário read ou line faz ou o GNU sed com a opção -u .

Você pode substituir head -n 20 por gsed -u 20q se quiser esse comportamento.

Embora aqui você prefira querer:

sed -e 1b -e '$b' -e d

em vez disso. Aqui, apenas uma chamada de ferramenta, portanto, nenhum problema com um buffer interno que não pode ser compartilhado entre duas chamadas de ferramenta. Note, entretanto, que para arquivos grandes, ele será menos eficiente, pois sed lê todo o arquivo, enquanto que para arquivos pesquisáveis tail pularia a maior parte dele procurando próximo ao final do arquivo.

Veja a discussão relacionada sobre buffering em Por que usar um loop de shell para processar texto é considerado uma prática ruim? .

Observe que tail deve gerar a saída do fluxo no stdin. Embora, como uma otimização e para arquivos pesquisáveis, as implementações possam buscar o final do arquivo para obter os dados finais de lá, não é permitido retornar a um ponto que seria antes da posição inicial no momento tail foi invocado (Busybox tail costumava ter esse bug).

Então, por exemplo, em:

{ cat; tail -n 1; } < file

Mesmo que tail possa voltar para a última linha de file , isso não acontece. Seu stdin é um fluxo vazio, pois cat deixou o cursor no final do arquivo; não é permitido recuperar dados desse fluxo procurando mais para trás no arquivo.

(Texto acima riscado pendente de esclarecimento pelo Grupo Aberto e considerando que não é feito corretamente por várias implementações)

¹ O head embutido de ksh93 (habilitado se você colocar /opt/ast/bin à frente de $PATH ), para sockets (um tipo de arquivos não-pesquisáveis) em vez disso espreita na entrada (usando recvfrom(..., MSG_PEEK) ) antes de realmente ler para ver quanto precisa ler para se certificar de que não lê muito. E volta a ler um byte de cada vez para outros tipos de arquivos. Isso é um pouco mais eficiente e acredito que seja a principal razão pela qual ele implementa seus pipes com socketpair() s em vez de pipe() . Note que não é completamente à prova de erros pois existe uma condição de corrida que pode ser desencadeada se outro processo for lido a partir do socket entre o peek e o read .

    
por 29.10.2015 / 18:29

Tags