Perché 'sed q' funziona diversamente quando si legge da una pipe?


25

Ho creato un file di test denominato "test" che contiene quanto segue:

xxx
yyy
zzz

Ho eseguito il comando:

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

e ho ottenuto:

xxx
yyy
aaa
zzz

Quindi ho corso:

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

e ottenuto:

xxx
yyy
aaa

Domanda

sedlegge e stampa fino a quando non incontra una riga con 'y', quindi si ferma. Nel primo caso, ma non nel secondo, il gatto legge e stampa il resto.

Qualcuno può spiegare quale fenomeno sta dietro questa differenza di comportamento?

Ho anche notato che funziona così in Ubuntu 16.04 e Centos 6 ma in Centos 7 nessuno dei due comandi stampa 'zzz'.


La mia ipotesi è che cat(nella sotto shell) possa riutilizzare il descrittore di file nel primo caso, perché stdin è associato a un file reale. Nel secondo caso, stdin proviene da una pipe e non da un file reale. Si noti che anche (sed '/y/ q'; echo aaa; cat) < <(cat test)non stampa zzz.
Martin Nyolt,

1
Un esempio più semplice: (head -n1; head -n1) < testecat test | (head -n1; head -n1)
Martin Nyolt il

Risposte:


22

Quando il file di input è ricercabile (come la lettura da un file normale) o non ricercabile (come la lettura da una pipe), sed(e altre utilità standard) si comporteranno in modo diverso (Leggi la INPUT FILESsezione in questo link ).

Citazione dal documento:

Quando un'utilità standard legge un file di input ricercabile e termina senza errori prima che raggiunga la fine del file, l'utilità deve assicurarsi che l'offset del file nella descrizione del file aperto sia posizionato correttamente dopo l'ultimo byte elaborato dall'utilità.

Quindi in:

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

sedeseguito il qcomando uit prima di raggiungere EOF, quindi ha lasciato l'offset del file all'inizio della zzzlinea, quindi catpuò continuare a stampare le linee rimanenti (GNU sed non è conforme POSIX in alcune condizioni, vedere sotto).

E proseguendo dal documento:

Per i file non ricercabili, lo stato dell'offset del file nella descrizione del file aperto per quel file non è specificato

In questo caso, il comportamento non è specificato. La maggior parte degli strumenti standard, include sedconsumerà l'input il più possibile. Legge passare la yyylinea e quit senza ripristinare l'offset del file, quindi nulla è lasciato per cat.


GNU sednon è conforme allo standard, dipende dall'implementazione stdio del sistema e dalla versione glibc:

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

Qui, il risultato è stato ottenuto da Mac OSX 10.11.6, macchine virtuali Centos 7.2 - glibc 2.17, Ubuntu 14.04 - glibc 2.19, che vengono eseguiti su Openstack con backend CEPH.

Su tali sistemi, è possibile utilizzare l' -uopzione per ottenere il comportamento standard:

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

e per tubo:

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

che porta a prestazioni terribilmente inefficienti, perché seddeve leggere un byte alla volta. Un output parziale da 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
...

1
Per GNU sed, questo dipende dall'implementazione stdio del sistema. Sui sistemi GNU (con GNU libc), GNU sedsarà conforme exit()e cercherà i file gestiti da stdio.
Stéphane Chazelas,

@ StéphaneChazelas: come verificarlo? Con il mio Centos 7.2, Ubuntu 14.04 VM, sednon è conforme, il mio laptop manjaro ha, tutti hanno la stessa sed versione 4.2.2
cuonglm

@ StéphaneChazelas: Sembra che sia successo qualcosa sotto il cofano. Sulle mie macchine virtuali, strace -f sh -c '{ sed "/y/q"; echo aaa; cat; } <test'mostra che no è lseek()stato eseguito, mentre nel mio manjaro è lseek()stato chiamato prima exit_group().
cuonglm,

Suppongo che dipenda dalla versione di GNU libc. Puoi testare con un main() { char buf[999]; gets(buf); }'programma.
Stéphane Chazelas,

1
@ StéphaneChazelas: confermato. Entrambe le mie macchine virtuali hanno 2.17 e 2.19, mentre quella nel mio manjaro è 2.23. Questo è considerato un bug di glibc? Hai qualche informazione sul cambiamento tra le versioni di glibc
cuonglm,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.