Le
echo one; echo two > >(cat); echo three;
la commande donne une sortie inattendue.
J'ai lu ceci :Comment la substitution de processus est-elle implémentée dans bash ? et de nombreux autres articles sur la substitution de processus sur Internet, mais je ne comprends pas pourquoi il se comporte de cette façon.
Résultat attendu :
one
two
three
Sortie réelle :
prompt$ echo one; echo two > >(cat); echo three;
one
three
prompt$ two
De plus, ces deux commandes devraient être équivalentes de mon point de vue, mais ce n'est pas le cas :
##### 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
Pourquoi je pense qu'ils devraient être les mêmes? Parce que les deux connectent le seq
sortie vers le cat
entrée via le canal anonyme - Wikipedia, substitution de processus.
Question : Pourquoi se comporte-t-il ainsi ? Où est mon erreur ? La réponse complète est souhaitée (avec explication de la façon dont le bash
le fait sous le capot).
Réponse acceptée :
Oui, en bash
comme dans ksh
(d'où vient la fonctionnalité), les processus à l'intérieur de la substitution de processus ne sont pas attendus (avant d'exécuter la commande suivante dans le script).
pour un <(...)
un, c'est généralement bien comme dans :
cmd1 <(cmd2)
le shell attendra cmd1
et cmd1
attendra généralement cmd2
en raison de sa lecture jusqu'à la fin du fichier sur le canal qui est remplacé, et cette fin de fichier se produit généralement lorsque cmd2
meurt. C'est la même raison pour laquelle plusieurs shells (pas bash
) ne vous embêtez pas à attendre cmd2
dans cmd2 | cmd1
.
Pour cmd1 >(cmd2)
, cependant, ce n'est généralement pas le cas, car il s'agit plutôt de cmd2
qui attend généralement cmd1
il sortira donc généralement après.
C'est corrigé dans zsh
qui attend cmd2
là (mais pas si vous l'écrivez comme cmd1 > >(cmd2)
et cmd1
n'est pas intégré, utilisez {cmd1} > >(cmd2)
à la place comme documenté).
ksh
n'attend pas par défaut, mais vous permet de l'attendre avec le wait
intégré (il rend également le pid disponible dans $!
, bien que cela n'aide pas si vous faites cmd1 >(cmd2) >(cmd3)
)
rc
(avec le cmd1 >{cmd2}
syntaxe), identique à ksh
sauf que vous pouvez obtenir les pids de tous les processus d'arrière-plan avec $apids
.
es
(également avec cmd1 >{cmd2}
) attend cmd2
comme dans zsh
, et attend également cmd2
dans <{cmd2}
traiter les redirections.
bash
fait le pid de cmd2
(ou plus exactement du sous-shell car il exécute cmd2
dans un processus enfant de ce sous-shell même s'il s'agit de la dernière commande) disponible dans $!
, mais ne vous laisse pas attendre.
Si vous devez utiliser bash
, vous pouvez contourner le problème en utilisant une commande qui attendra les deux commandes avec :
{ { cmd1 >(cmd2); } 3>&1 >&4 4>&- | cat; } 4>&1
Cela fait à la fois cmd1
et cmd2
ont leur fd 3 ouvert sur un tuyau. cat
attendra la fin du fichier à l'autre extrémité, donc ne sortira généralement que lorsque les deux cmd1
et cmd2
sont mortes. Et le shell attendra ce cat
commande. Vous pouvez voir cela comme un filet pour attraper la fin de tous les processus d'arrière-plan (vous pouvez l'utiliser pour d'autres choses démarrées en arrière-plan comme avec &
, des coprocs ou même des commandes qui s'arrière-plan à condition qu'ils ne ferment pas tous leurs descripteurs de fichiers comme le font généralement les démons).
Notez que grâce à ce processus de sous-shell gaspillé mentionné ci-dessus, cela fonctionne même si cmd2
ferme son fd 3 (les commandes ne le font généralement pas, mais certaines comme sudo
ou ssh
faire). Les futures versions de bash
peut éventuellement y faire l'optimisation comme dans d'autres shells. Ensuite, vous auriez besoin de quelque chose comme :
{ { cmd1 >(sudo cmd2; exit); } 3>&1 >&4 4>&- | cat; } 4>&1
Pour s'assurer qu'il y a encore un processus shell supplémentaire avec ce fd 3 ouvert en attente de ce sudo
commande.
Notez que cat
ne lira rien (puisque les processus n'écrivent pas sur leur fd 3). C'est juste là pour la synchronisation. Il ne fera qu'un seul read()
appel système qui retournera sans rien à la fin.
Vous pouvez en fait éviter d'exécuter cat
en utilisant une substitution de commande pour faire la synchronisation du tube :
{ unused=$( { cmd1 >(cmd2); } 3>&1 >&4 4>&-); } 4>&1
Cette fois, c'est le shell au lieu de cat
qui lit depuis le tube dont l'autre extrémité est ouverte sur fd 3 de cmd1
et cmd2
. Nous utilisons une affectation de variable afin que le statut de sortie de cmd1
est disponible en $?
.
Ou vous pouvez effectuer la substitution de processus à la main, puis vous pouvez même utiliser le sh
de votre système car cela deviendrait la syntaxe standard du shell :
{ cmd1 /dev/fd/3 3>&1 >&4 4>&- | cmd2 4>&-; } 4>&1
mais notez comme indiqué précédemment que tous les sh
les implémentations attendraient cmd1
après cmd2
a fini (même si c'est mieux que l'inverse). Cette fois, $?
contient le statut de sortie de cmd2
; bien que bash
et zsh
faire cmd1
Le statut de sortie de est disponible dans ${PIPESTATUS[0]}
et $pipestatus[1]
respectivement (voir aussi le pipefail
option dans quelques shells donc $?
peut signaler la défaillance de composants de tuyauterie autres que le dernier)
Notez que yash
a des problèmes similaires avec son processus de redirection fonctionnalité. cmd1 >(cmd2)
s'écrirait cmd1 /dev/fd/3 3>(cmd2)
là. Mais cmd2
n'est pas attendu et vous ne pouvez pas utiliser wait
de l'attendre non plus et son pid n'est pas rendu disponible dans le $!
variables non plus. Vous utiliseriez les mêmes solutions de contournement que pour bash
.