le
echo one; echo two > >(cat); echo three;
donne une sortie inattendue.
Je lis 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.
Production attendue:
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 elles ne le font pas:
##### 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, ils devraient être les mêmes? Parce que les deux connectent la seq
sortie à l' cat
entrée via le canal anonyme - Wikipedia, Substitution de processus .
Question: Pourquoi il se comporte de cette façon? Où est mon erreur? La réponse complète est souhaitée (avec explication de la façon dont bash
cela se fait sous le capot).
bash
process-substitution
MiniMax
la source
la source
Réponses:
Oui,
bash
comme dansksh
(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
<(...)
, c'est généralement bien comme dans:le shell attendra
cmd1
etcmd1
attendra généralementcmd2
en raison de sa lecture jusqu'à la fin du fichier sur le canal qui est substitué, et cette fin de fichier se produit généralement lors de lacmd2
mort. C'est la même raison plusieurs obus (nonbash
) ne prennent pas la peine d' attendre pourcmd2
encmd2 | cmd1
.Car
cmd1 >(cmd2)
, cependant, ce n'est généralement pas le cas, car c'est pluscmd2
qui attend généralementcmd1
là-bas, donc il sortira généralement après.C'est fixé dans
zsh
qui attendcmd2
là (mais pas si vous écrivez commecmd1 > >(cmd2)
etcmd1
n'est pas BuiltIn, utilisez{cmd1} > >(cmd2)
plutôt comme documenté ).ksh
n'attend pas par défaut, mais vous permet de l'attendre avec la fonctionwait
intégrée (cela rend également le pid disponible dans$!
, bien que cela n'aide pas si vous le faitescmd1 >(cmd2) >(cmd3)
)rc
(avec lacmd1 >{cmd2}
syntaxe), identique à ce queksh
vous pouvez obtenir les pids de tous les processus d'arrière-plan avec$apids
.es
(également aveccmd1 >{cmd2}
) attendcmd2
comme danszsh
, et attend également les redirectionscmd2
en<{cmd2}
cours.bash
ne rend pas disponible le pid decmd2
(ou plus exactement du sous-shell car il s'exécutecmd2
dans un processus enfant de ce sous-shell même s'il s'agit de la dernière commande)$!
, mais ne vous laisse pas l'attendre.Si vous devez utiliser
bash
, vous pouvez contourner le problème en utilisant une commande qui attendra les deux commandes avec:Cela fait les deux
cmd1
etcmd2
ouvre leur fd 3 à un tuyau.cat
attendra la fin du fichier à l'autre extrémité, donc ne sortira généralement que lorsque les deuxcmd1
etcmd2
seront morts. Et le shell attendra cettecat
commande. Vous pouvez voir cela comme un filet pour attraper la fin de tous les processus en arrière-plan (vous pouvez l'utiliser pour d'autres choses démarrées en arrière-plan comme avec&
, les coprocs ou même les commandes qui se mettent en arrière-plan à condition de ne pas fermer 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, il fonctionne même si
cmd2
ferme son fd 3 (les commandes ne font généralement pas cela, mais certains aimentsudo
oussh
font). Les futures versions debash
pourraient éventuellement faire l'optimisation comme dans d'autres shells. Ensuite, vous auriez besoin de quelque chose comme:Pour vous assurer qu'il y a encore un processus shell supplémentaire avec ce fd 3 open en attente de cette
sudo
commande.Notez que
cat
cela ne lira rien (puisque les processus n'écrivent pas sur leur fd 3). Il est juste là pour la synchronisation. Il ne fera qu'un seulread()
appel système qui reviendra sans rien à la fin.Vous pouvez réellement éviter de courir
cat
en utilisant une substitution de commande pour effectuer la synchronisation des canaux:Cette fois, c'est le shell au lieu de
cat
cela qui lit le tuyau dont l'autre extrémité est ouverte sur le fd 3 decmd1
etcmd2
. Nous utilisons une affectation de variable afin que l'état de sortie decmd1
soit disponible dans$?
.Ou vous pouvez faire la substitution de processus à la main, puis vous pouvez même utiliser votre système
sh
car cela deviendrait la syntaxe standard du shell:mais notez comme indiqué précédemment que toutes les
sh
implémentations n'attendraient pascmd1
après lacmd2
fin (bien que ce soit mieux que l'inverse). Cette fois,$?
contient le statut de sortie decmd2
; cependantbash
etzsh
rendrecmd1
le statut de sortie de disponible dans${PIPESTATUS[0]}
et$pipestatus[1]
respectivement (voir également l'pipefail
option dans quelques coquilles afin de$?
pouvoir signaler la défaillance de composants de tuyaux autres que le dernier)Notez qu'il
yash
a des problèmes similaires avec sa fonction de redirection de processus .cmd1 >(cmd2)
serait écritcmd1 /dev/fd/3 3>(cmd2)
là-bas. Maiscmd2
n'est pas attendu et vous ne pouvez pas nonwait
plus l'attendre et son pid n'est pas non plus disponible dans la$!
variable. Vous utiliseriez les mêmes contournements que pourbash
.la source
echo one; { { echo two > >(cat); } 3>&1 >&4 4>&- | cat; } 4>&1; echo three;
, puis je l'ai simplifiéecho one; echo two > >(cat) | cat; echo three;
et il affiche également les valeurs dans le bon ordre. Toutes ces manipulations de descripteurs3>&1 >&4 4>&-
sont-elles nécessaires? De plus, je ne comprends pas>&4 4>&
- nous sommes redirigésstdout
vers le quatrième fd, puis fermons le quatrième fd, puis utilisons-le à nouveau4>&1
. Pourquoi en avait-il besoin et comment cela fonctionne-t-il? Peut-être, je devrais créer une nouvelle question sur ce sujet?cmd1
etcmd2
, le point avec la petite danse avec le descripteur de fichier est de restaurer les originaux et d'utiliser uniquement le tuyau supplémentaire pour l' attente au lieu de canaliser également la sortie des commandes.4>&1
crée un descripteur de fichier (fd) 4 pour la liste de commandes des accolades externes et le rend égal à la sortie standard des accolades externes. Les accolades intérieures ont stdin / stdout / stderr configuré automatiquement pour se connecter aux accolades extérieures. Cependant,3>&1
fd 3 se connecte au stdin des accolades externes.>&4
connecte la sortie standard des accolades intérieures aux accolades extérieures fd 4 (celle que nous avons créée auparavant).4>&-
ferme fd 4 des accolades intérieures (puisque la sortie standard des accolades intérieures est déjà connectée à la fd 4 des accolades).4>&1
est exécutée en premier, avant les autres redirections, donc vous ne "réutilisez4>&1
" plus. Dans l'ensemble, les accolades internes envoient des données à leur sortie standard, qui a été remplacée par le fd 4 qui leur a été donné. Le fd 4 donné aux accolades intérieures est le fd 4 des accolades extérieures, qui est égal à la sortie d'origine des accolades extérieures.4>5
"4 va à 5", mais vraiment "fd 4 est remplacé par fd 5". Et avant l'exécution, les fd 0/1/2 sont automatiquement connectés (avec tout fd de la coque externe), et vous pouvez les remplacer comme vous le souhaitez. C'est du moins mon interprétation de la documentation bash. Si vous avez compris quelque chose sur ce , LMK.Vous pouvez diriger la deuxième commande vers une autre
cat
, qui attendra la fermeture de son canal d'entrée. Ex:Court et simple.
==========
Aussi simple que cela puisse paraître, il se passe beaucoup de choses dans les coulisses. Vous pouvez ignorer le reste de la réponse si vous ne souhaitez pas savoir comment cela fonctionne.
Lorsque c'est le cas
echo two > >(cat); echo three
,>(cat)
est bifurqué par le shell interactif et s'exécute indépendamment deecho two
. Ainsi, seecho two
termine, puisecho three
s'exécute, mais avant les>(cat)
finitions. Lorsquebash
obtient des données à partir du>(cat)
moment où il ne s'y attendait pas (quelques millisecondes plus tard), il vous donne cette situation semblable à une invite où vous devez appuyer sur la nouvelle ligne pour revenir au terminal (comme si un autre utilisateurmesg
vous avait édité).Cependant, étant donné
echo two > >(cat) | cat; echo three
, deux sous-coquilles sont générées (selon la documentation du|
symbole).Un sous-shell nommé A est pour
echo two > >(cat)
, et un sous-shell nommé B est pourcat
. A est automatiquement connecté à B (la sortie standard de A est la sortie standard de B). Ensuite,echo two
et>(cat)
commencez à exécuter.>(cat)
stdout de est défini sur stdout de A, qui est égal à stdin de B. Après avoirecho two
terminé, A sort, fermant sa sortie standard. Cependant,>(cat)
tient toujours la référence au stdin de B. Le secondcat
stdin tient le stdin de B, et celacat
ne sortira pas tant qu'il n'aura pas vu d'EOF. Un EOF n'est donné que lorsque personne n'a plus le fichier ouvert en mode écriture, donc>(cat)
stdout bloque le secondcat
. B attend cette secondecat
. Depuis saecho two
sortie,>(cat)
obtient finalement un EOF, donc>(cat)
vide son tampon et quitte. Personne ne tientcat
plus le stdin de B / seconde , donc le secondcat
lit un EOF (B ne lit pas du tout son stdin, il s'en fiche). Cet EOF amène le secondcat
à vider son tampon, à fermer sa sortie standard et à sortir, puis B se termine parce qu'il estcat
sorti et B attendcat
.Une mise en garde est que bash génère également un sous-shell pour
>(cat)
! Pour cette raison, vous verrez queecho two > >(sleep 5) | cat; echo three
attendra encore 5 secondes avant de s'exécuter
echo three
, même s'ilsleep 5
ne tient pas le stdin de B. C'est parce qu'un sous-shell caché que C a généré>(sleep 5)
attendsleep
et que C contient le stdin de B. Vous pouvez voir commentecho two > >(exec sleep 5) | cat; echo three
N'attendra pas cependant, car il
sleep
ne contient pas le stdin de B, et il n'y a pas de sous-shell fantôme C qui détient le stdin de B (l'exécutif forcera le sommeil à remplacer C, au lieu de bifurquer et de faire attendre Csleep
). Indépendamment de cette mise en garde,echo two > >(exec cat) | cat; echo three
exécutera toujours correctement les fonctions dans l'ordre, comme décrit précédemment.
la source
A
n'attend pas l'cat
apparition>(cat)
. Comme je le mentionne dans ma réponse, la raison pour laquelle lesecho two > >(sleep 5 &>/dev/null) | cat; echo three
sortiesthree
après 5 secondes sont dues au fait que les versions actuelles debash
gaspillent un processus shell supplémentaire>(sleep 5)
qui attendsleep
et que ce processus a toujours une sortie standardpipe
qui empêche la secondecat
de se terminer. Si vous le remplacez parecho two > >(exec sleep 5 &>/dev/null) | cat; echo three
pour éliminer ce processus supplémentaire, vous constaterez qu'il revient immédiatement.echo two > >(sleep 5 &>/dev/null)
le minimum obtient son propre sous-shell. Est-ce un détail d'implémentation non documenté qui provoque également l'sleep 5
obtention de son propre sous-shell? S'il est documenté, ce serait un moyen légitime de le faire avec moins de caractères (à moins qu'il n'y ait une boucle serrée, je ne pense pas que quiconque remarquera des problèmes de performances avec un sous-shell ou un chat) `. Si ce n'est pas documenté, alors rip, un bon hack, ne fonctionnera pas sur les futures versions.$(...)
,<(...)
impliquent en effet un sous-shell, mais ksh93 ou zsh exécuterait la dernière commande dans ce sous-shell dans le même processus,bash
ce qui n'est pas la raison pour laquelle il y a encore un autre processus qui maintient le tuyau ouvert pendant qu'ilsleep
exécute un qui ne maintient pas le tuyau ouvert. Les futures versions debash
pourraient implémenter une optimisation similaire.exec
, cela fonctionne comme prévu.