Il
echo one; echo two > >(cat); echo three;
il comando fornisce un output imprevisto.
Ho letto questo:come viene implementata la sostituzione del processo in bash? e molti altri articoli sulla sostituzione dei processi su Internet, ma non capisco perché si comporti in questo modo.
Risultato previsto:
one
two
three
Risultato reale:
prompt$ echo one; echo two > >(cat); echo three;
one
three
prompt$ two
Inoltre, questi due comandi dovrebbero essere equivalenti dal mio punto di vista, ma non lo fanno:
##### first command - the pipe is used.
prompt$ seq 1 5 | cat
1
2
3
4
5
##### second command - the process substitution and redirection are used.
prompt$ seq 1 5 > >(cat)
prompt$ 1
2
3
4
5
Perché penso che dovrebbero essere gli stessi? Perché entrambi connettono il seq
output al cat
input attraverso la pipe anonima – Wikipedia, Processo di sostituzione.
Domanda: Perché si comporta in questo modo? Dov'è il mio errore? La risposta esauriente è desiderata (con spiegazione di come bash
lo fa sotto il cofano).
Risposta accettata:
Sì, in bash
come in ksh
(da dove proviene la funzione), i processi all'interno della sostituzione del processo non sono attesi (prima di eseguire il comando successivo nello script).
per un <(...)
uno, di solito va bene come in:
cmd1 <(cmd2)
la shell attenderà cmd1
e cmd1
in genere attende cmd2
in virtù della lettura fino alla fine del file sulla pipe che viene sostituita, e quella fine del file si verifica in genere quando cmd2
muore. Questo è lo stesso motivo per cui diverse shell (non bash
) non preoccuparti di aspettare cmd2
in cmd2 | cmd1
.
Per cmd1 >(cmd2)
, tuttavia, in genere non è così, poiché è più cmd2
che in genere attende cmd1
lì quindi generalmente uscirà dopo.
È stato risolto in zsh
che attende cmd2
lì (ma non se lo scrivi come cmd1 > >(cmd2)
e cmd1
non è integrato, usa {cmd1} > >(cmd2)
invece come documentato).
ksh
non attende per impostazione predefinita, ma ti consente di aspettarlo con wait
builtin (rende anche il pid disponibile in $!
, anche se ciò non aiuta se esegui cmd1 >(cmd2) >(cmd3)
)
rc
(con il cmd1 >{cmd2}
sintassi), come ksh
tranne per il fatto che puoi ottenere i pid di tutti i processi in background con $apids
.
es
(anche con cmd1 >{cmd2}
) attende cmd2
come in zsh
e attende anche cmd2
in <{cmd2}
reindirizzamenti del processo.
bash
fa il pid di cmd2
(o più esattamente della subshell poiché esegue cmd2
in un processo figlio di quella subshell anche se è l'ultimo comando presente) disponibile in $!
, ma non ti fa aspettare.
Se devi usare bash
, puoi aggirare il problema utilizzando un comando che attenderà entrambi i comandi con:
{ { cmd1 >(cmd2); } 3>&1 >&4 4>&- | cat; } 4>&1
Ciò rende entrambi cmd1
e cmd2
hanno il loro fd 3 aperto su una pipe. cat
attenderà la fine del file all'altra estremità, quindi in genere uscirà solo quando entrambi cmd1
e cmd2
sono morti. E la shell aspetterà quel cat
comando. Potresti vederlo come una rete per catturare la terminazione di tutti i processi in background (puoi usarlo per altre cose avviate in background come con &
, coproc o anche comandi in background a condizione che non chiudano tutti i descrittori di file come fanno in genere i daemon).
Nota che grazie a quel processo di subshell sprecato menzionato sopra, funziona anche se cmd2
chiude il suo fd 3 (i comandi di solito non lo fanno, ma alcuni come sudo
o ssh
fare). Versioni future di bash
potrebbe eventualmente fare l'ottimizzazione lì come in altre shell. Allora avresti bisogno di qualcosa come:
{ { cmd1 >(sudo cmd2; exit); } 3>&1 >&4 4>&- | cat; } 4>&1
Per assicurarti che ci sia ancora un processo di shell aggiuntivo con quell'fd 3 aperto in attesa di quel sudo
comando.
Nota che cat
non leggerà nulla (poiché i processi non scrivono sul loro fd 3). Serve solo per la sincronizzazione. Farà solo un read()
chiamata di sistema che alla fine restituirà senza nulla.
Puoi effettivamente evitare di eseguire cat
utilizzando una sostituzione di comando per eseguire la sincronizzazione della pipe:
{ unused=$( { cmd1 >(cmd2); } 3>&1 >&4 4>&-); } 4>&1
Questa volta, è la shell invece di cat
che sta leggendo dalla pipe la cui altra estremità è aperta su fd 3 di cmd1
e cmd2
. Stiamo utilizzando un'assegnazione variabile, quindi lo stato di uscita di cmd1
è disponibile in $?
.
Oppure potresti eseguire manualmente la sostituzione del processo e quindi potresti persino utilizzare sh
del tuo sistema poiché diventerebbe la sintassi della shell standard:
{ cmd1 /dev/fd/3 3>&1 >&4 4>&- | cmd2 4>&-; } 4>&1
tuttavia, come notato in precedenza, non tutti i sh
le implementazioni attenderebbero cmd1
dopo cmd2
è finito (anche se è meglio del contrario). Quella volta, $?
contiene lo stato di uscita di cmd2
; anche se bash
e zsh
crea cmd1
Stato di uscita disponibile in ${PIPESTATUS[0]}
e $pipestatus[1]
rispettivamente (vedi anche il pipefail
opzione in alcune shell quindi $?
può segnalare il guasto di componenti del tubo diversi dall'ultimo)
Nota che yash
ha problemi simili con il suo processo di reindirizzamento caratteristica. cmd1 >(cmd2)
verrebbe scritto cmd1 /dev/fd/3 3>(cmd2)
là. Ma cmd2
non è atteso e non puoi usare wait
aspettare anche lui e il suo pid non viene reso disponibile nel $!
anche variabile. Utilizzeresti le stesse soluzioni alternative di bash
.