2015-12-17 13 views
12

Utilizzando la sostituzione del processo di bash, voglio eseguire contemporaneamente due diversi comandi su un file. In questo esempio non è necessario, ma immagina che "cat/usr/share/dict/words" sia un'operazione molto costosa come la decompressione di un file da 50 gb.Risultati errati con sostituzione e coda di processo bash?

cat /usr/share/dict/words | tee >(head -1 > h.txt) >(tail -1 > t.txt) > /dev/null 

Dopo questo comando mi aspetterei h.txt per contenere la prima linea delle parole file "A", e t.txt per contenere l'ultima riga del file "Zyzzogeton".

Tuttavia, ciò che effettivamente accade è che h.txt contiene "A" ma t.txt contiene "argillaceo" che è circa il 5% nel file.

Perché succede? Sembra che il processo "coda" stia terminando presto o che i flussi si confondano.

esecuzione di un altro comando simile come questo si comporta come previsto:

cat /usr/share/dict/words | tee >(grep ^a > a.txt) >(grep ^z > z.txt) > /dev/null 

Dopo questo comando mi aspetto a.txt per contenere tutte le parole che iniziano con "a", mentre z.txt contiene tutti le parole che iniziano con "z", che è esattamente quello che è successo.

Quindi, perché non funziona con "coda" e con quali altri comandi non funzionerà?

+1

Penso che questo è legato alla http://stackoverflow.com/questions/4489139/bash-process-replacement-and-syncing che suggerisce che i processi con nella sostituzione escono non appena termina il comando esterno, ma sinceramente non sono in grado di dimostrare che è il problema corrente con qualsiasi comando I Ho provato fino ad ora –

risposta

10

Ok, che sembra accadere è che una volta che il comando head -1 termina esce e che provoca tee ottenere un SIGPIPE cerca di scrivere nel pipa denominata che la configurazione sostituzione di processo che genera un EPIPE e secondo man 2 write anche genera SIGPIPE nel processo di scrittura, che provoca l'uscita da tee e che obbliga l'tail -1 a uscire immediatamente e lo cat a sinistra ottiene anche SIGPIPE.

Possiamo vedere questo un po 'meglio se si aggiunge un po' di più per il processo con head e rendere l'output sia più prevedibile e anche scritto al stderr senza fare affidamento sul tee:

for i in {1..30}; do echo "$i"; echo "$i" >&2; sleep 1; done | tee >(head -1 > h.txt; echo "Head done") >(tail -1 > t.txt) >/dev/null 

che quando io eseguirlo mi ha dato l'output:

1 
Head done 
2 

quindi ottenuto solo 1 più iterazione del ciclo prima che tutto esce (anche se ancora t.txt basta 1 dentro). Se poi fatto

echo "${PIPESTATUS[@]}" 

vediamo

141 141 

che this question legami con SIGPIPE in un modo molto simile a quello che stiamo vedendo qui.

I manutentori coreutils hanno aggiunto questo come esempio al loro tee "gotchas" per i posteri futuri.

Per una discussione con gli sviluppatori su come questo si inserisce in conformità POSIX si può vedere il (notabug chiuso) relazione http://debbugs.gnu.org/cgi/bugreport.cgi?bug=22195

Se si ha accesso a GNU versione 8.24 hanno aggiunto alcune opzioni (non in POSIX) che possono essere d'aiuto come -p o --output-error=warn. Senza che si può prendere un po 'un rischio, ma ottenere la funzionalità desiderata nella questione intrappolando e ignorando SIGPIPE:

trap '' PIPE 
for i in {1..30}; do echo "$i"; echo "$i" >&2; sleep 1; done | tee >(head -1 > h.txt; echo "Head done") >(tail -1 > t.txt) >/dev/null 
trap - PIPE 

avranno i risultati attesi sia in h.txt e t.txt, ma se qualcosa è accaduto che SIGPIPE ricercato per essere gestito correttamente, si sarebbe sfortunati con questo approccio.

Un'altra opzione potrebbe essere quella di hacky azzerare t.txt prima di iniziare, allora non lasciare che l'elenco finitura head processo fino a quando non è diverso da zero lunghezza:

> t.txt; for i in {1..10}; do echo "$i"; echo "$i" >&2; sleep 1; done | tee >(head -1 > h.txt; echo "Head done"; while [ ! -s t.txt ]; do sleep 1; done) >(tail -1 > t.txt; date) >/dev/null 
+1

Il comportamento specificato da POSIX per 'tee' è che continui a funzionare anche se uno dei suoi lettori esce - quindi se stai vedendo qualcosa di contrario, questo è in realtà un bug. –

+0

"Se una scrittura su qualsiasi operando di file aperto correttamente fallisce, scrive su altri operandi di file aperti correttamente e l'output standard deve continuare, ma lo stato di uscita deve essere diverso da zero, altrimenti si applicano le azioni predefinite specificate in Default descrizione utilità." - http://pubs.opengroup.org/onlinepubs/9699919799/utilities/tee.html –

+0

@CharlesDuffy bene i risultati sopra riportati sono per una versione precedente, suppongo, 8.5, posso riprovare. Inoltre, non ho approfondito abbastanza per sapere se la sostituzione del processo si presenta come un handle di file chiuso o se alza effettivamente SIGPIPE quando termina quel processo, avrei più lavoro da fare prima di inviare una segnalazione di bug presumo –