Por que 'sed q' funciona de maneira diferente ao ler de um cachimbo?

25

Criei um arquivo de teste chamado 'test' que contém o seguinte:

xxx
yyy
zzz

Eu executei o comando:

(sed '/y/ q'; echo aaa; cat) < test

e eu tenho:

xxx
yyy
aaa
zzz

Então eu corri:

cat test | (sed '/y/ q'; echo aaa; cat)

e pegou:

xxx
yyy
aaa

Questão

sedlê e imprime até encontrar uma linha com 'y' e depois para. No primeiro caso, mas não no segundo, o gato lê e imprime o resto.

Alguém pode explicar que fenômeno está por trás dessa diferença de comportamento?

Também notei que funciona dessa maneira no Ubuntu 16.04 e Centos 6, mas no Centos 7 nenhum comando imprime 'zzz'.

Antti Kuusela
fonte
Meu palpite é que cat(no sub shell) pode reutilizar o descritor de arquivo no primeiro caso, porque stdin está vinculado a um arquivo real. No segundo caso, stdin é de um pipe e não de um arquivo real. Observe que também (sed '/y/ q'; echo aaa; cat) < <(cat test)não imprime zzz.
precisa saber é o seguinte
11
Um exemplo mais simples: (head -n1; head -n1) < testecat test | (head -n1; head -n1)
Martin Nyolt

Respostas:

22

Quando o arquivo de entrada é procurável (como a leitura de um arquivo normal) ou não pesquisável (como a leitura de um canal ), sed(e outros utilitários padrão) se comportam de maneira diferente ( INPUT FILESseção Leia neste link ).

Citação do documento:

Quando um utilitário padrão lê um arquivo de entrada procurável e termina sem erro antes de chegar ao final do arquivo, o utilitário deve garantir que o deslocamento do arquivo na descrição do arquivo aberto seja posicionado corretamente logo após o último byte processado pelo utilitário.

Então em:

(sed '/y/ q'; echo aaa; cat) < test

sedexecutou o qcomando uit antes de atingir o EOF, deixando o deslocamento do arquivo no início da zzzlinha, para catcontinuar imprimindo as linhas restantes (o GNU sed não é compatível com POSIX em alguma condição, veja abaixo).

E continuando do documento:

Para arquivos não procuráveis, o estado do deslocamento do arquivo na descrição do arquivo aberto para esse arquivo não é especificado

Nesse caso, o comportamento não é especificado. A maioria das ferramentas padrão, include sed, consumirá a entrada o máximo possível. Ele lê passar a yyylinha e qsair sem restaurar o deslocamento do arquivo, então não resta mais nada cat.


O GNU sednão é compatível com o padrão, depende da implementação stdio do sistema e da versão glibc:

$ (gsed '/y/ q'; echo aaa; cat) < test
xxx
yyy
aaa

Aqui, o resultado foi obtido no Mac OSX 10.11.6, máquinas virtuais Centos 7.2 - glibc 2.17, Ubuntu 14.04 - glibc 2.19, que são executadas no Openstack com back-end do CEPH.

Nesses sistemas, você pode usar a -uopção para obter o comportamento padrão:

(gsed -u '/y/ q'; echo aaa; cat) </tmp/test

e para tubo:

$ cat test | (gsed -u '/y/ q'; echo aaa; cat)
xxx
yyy
aaa
zzz

o que leva a um desempenho terrivelmente ineficiente, porque sedé necessário ler um byte de cada vez. Uma saída parcial de strace:

$ strace -fe read sh -c '{ sed -u "/y/q"; echo aaa; cat; } <test'
...
[pid  5248] read(3, "", 4096)           = 0
[pid  5248] read(0, "x", 1)             = 1
[pid  5248] read(0, "x", 1)             = 1
[pid  5248] read(0, "x", 1)             = 1
[pid  5248] read(0, "\n", 1)            = 1
xxx
[pid  5248] read(0, "y", 1)             = 1
[pid  5248] read(0, "y", 1)             = 1
[pid  5248] read(0, "y", 1)             = 1
[pid  5248] read(0, "\n", 1)            = 1
yyy
...
cuonglm
fonte
11
Para o GNU sed, isso depende da implementação do stdio do sistema. Nos sistemas GNU (com o GNU libc), o GNU sedserá compatível, assim como os exit()arquivos gerenciados pelo stdio.
Stéphane Chazelas
@ StéphaneChazelas: Como verificar isso? Com a minha Centos 7.2, o Ubuntu 14.04 VM, sednão for compatível, o meu laptop Manjaro faz, todos têm a mesma sed versão 4.2.2
cuonglm
@ StéphaneChazelas: Parece que algo aconteceu debaixo do capô. Nas minhas máquinas virtuais, strace -f sh -c '{ sed "/y/q"; echo aaa; cat; } <test'mostre que não lseek()foi realizado, enquanto no meu manjaro a lseek()foi chamado antes exit_group().
precisa saber é
Suponho que isso se deva à versão da GNU libc. Você pode testar com um main() { char buf[999]; gets(buf); }'programa.
Stéphane Chazelas
11
@ StéphaneChazelas: confirmado. Ambas as minhas VMs têm 2.17 e 2.19, enquanto a do meu manjaro é 2.23. Isso é considerado um bug da glibc? Você tem alguma informação sobre a mudança entre as versões glibc
cuonglm