Création de sous-coque Bash avec des accolades

31

Selon cela , placer une liste de commandes entre accolades entraîne l'exécution de la liste dans le contexte de shell actuel. Aucun sous-shell n'est créé .

Utiliser pspour voir cela en action

Il s'agit de la hiérarchie de processus pour un pipeline de processus exécuté directement sur la ligne de commande. 4398 est le PID du shell de connexion:

sleep 2 | ps -H;
  PID TTY          TIME CMD
   4398 pts/23   00:00:00 bash
   29696 pts/23   00:00:00   sleep
   29697 pts/23   00:00:00   ps

Suit maintenant la hiérarchie des processus pour un pipeline de processus entre accolades exécutés directement sur la ligne de commande. 4398 est le PID du shell de connexion. Elle est similaire à la hiérarchie ci-dessus prouvant que tout est exécuté dans le contexte actuel du shell :

{ sleep 2 | ps -H; }
   PID TTY          TIME CMD
    4398 pts/23   00:00:00 bash
    29588 pts/23   00:00:00   sleep
    29589 pts/23   00:00:00   ps

Maintenant, c'est la hiérarchie du processus lorsque le sleepdans le pipeline est lui-même placé à l'intérieur des accolades (donc deux niveaux d'accolades en tout)

{ { sleep 2; } | ps -H; }
  PID TTY          TIME CMD
   4398 pts/23   00:00:00 bash
   29869 pts/23   00:00:00   bash
   29871 pts/23   00:00:00     sleep
   29870 pts/23   00:00:00   ps

Pourquoi faut bash-il créer un sous-shell à exécuter sleepdans le 3ème cas lorsque la documentation indique que les commandes entre accolades sont exécutées dans le contexte actuel du shell?

iruvar
la source
Intéressant, je suppose que c'est parce que dans le 3ème cas, le groupe interne fait partie du pipeline, et donc il est exécuté en sous-shell par exemple, comme tout autre appel de fonction qui fait partie du pipeline. Est-ce que ça fait du sens?
Miroslav Koškár
2
Je ne dirais pas "Le shell doit" juste parce qu'il le fait ... Les pipelines ne sont pas exécutés dans le contexte du shell. Si le pipeline n'est composé que de commandes externes, la création de sous-processus suffit. { sleep 2 | command ps -H; }
Hauke ​​Laging du

Réponses:

26

Dans un pipeline, toutes les commandes s'exécutent simultanément (avec leur stdout / stdin connecté par des tuyaux) donc dans différents processus.

Dans

cmd1 | cmd2 | cmd3

Les trois commandes s'exécutent dans des processus différents, donc au moins deux d'entre elles doivent s'exécuter dans un processus enfant. Certains shells exécutent l'un d'eux dans le processus de shell actuel (s'il est intégré readou si le pipeline est la dernière commande du script), mais les bashexécute tous dans leur propre processus séparé (sauf avec l' lastpipeoption dans les bashversions récentes et dans certaines conditions spécifiques ).

{...}commandes de groupes. Si ce groupe fait partie d'un pipeline, il doit s'exécuter dans un processus distinct, tout comme une simple commande.

Dans:

{ a; b "$?"; } | c

Nous avons besoin d'un shell pour évaluer qu'il a; b "$?"s'agit d'un processus distinct, nous avons donc besoin d'un sous-shell. Le shell pourrait optimiser en ne forkant pas bcar c'est la dernière commande à être exécutée dans ce groupe. Certains obus le font, mais apparemment pas bash.

Stéphane Chazelas
la source
" Les trois commandes s'exécutent dans des processus différents, donc au moins deux d'entre elles doivent s'exécuter dans un sous-shell. " Pourquoi un sous-shell est-il nécessaire dans ce cas? Le shell parent ne peut-il pas générer des processus d'arbre? Ou, lorsque vous dites " doivent s'exécuter dans un sous-shell ", voulez-vous dire que le shell se bifurquera, puis exécutera pour chaque cmd1 cmd2 et cmd3? Si j'exécute cela, bash -c "sleep 112345 | cat | cat "je ne vois qu'un seul bash créé et ensuite 3 enfants sans aucun autre sous-entrelacement entrelacé.
Hakan Baba,
" Nous avons besoin d'un shell pour évaluer que a; b" $? "Est un processus distinct, nous avons donc besoin d'un sous-shell. " Pourriez-vous également développer le raisonnement? Pourquoi avons-nous besoin d'une sous-roue pour comprendre cela? Que faut-il pour comprendre cela? Je suppose que l'analyse est requise, mais quoi d'autre? . Le shell parent ne peut-il pas analyser a; b "$?"? Existe-t-il vraiment un besoin fondamental pour une sous-roue, ou peut-être s'agit-il d'une décision de conception / mise en œuvre sur bash?
Hakan Baba,
@HakanBaba, j'ai changé cela en "processus enfant" pour éviter toute confusion potentielle.
Stéphane Chazelas
1
@HakanBaba, l'analyse est effectuée dans le parent (le processus qui lit le code, celui qui a exécuté l'interpréteur à moins que ce code ne soit passé à eval), mais l'évaluation (exécutez la première commande, attendez-la, exécutez la seconde) est fait chez l'enfant, celui qui a stdout connecté au tuyau.
Stéphane Chazelas
dans { sleep 2 | ps -H; }le bash parent voit sleep 2que nécessite un fork / exec. Mais dans { { sleep 2; } | ps -H; }le bash parent voit { sleep 2; }en d'autres termes, du code bash. Il semble que le parent puisse gérer le fork / exec pour sleep 2mais génère un nouveau bash récursivement pour gérer le code bash rencontré. C'est ma compréhension, est-ce sensé?
Hakan Baba
19

L'imbrication des accolades semble indiquer que vous créez un niveau supplémentaire de portée qui nécessite l'appel d'un nouveau sous-shell. Vous pouvez voir cet effet avec la 2ème copie de Bash dans votre ps -Hsortie.

Seuls les processus stipulés dans le premier niveau des accolades sont exécutés dans le cadre de la coque Bash d'origine. Toutes les accolades bouclées imbriquées s'exécuteront dans leur propre shell Bash de portée.

Exemple

$ { { { sleep 20; } | sleep 20; } | ps -H; }
  PID TTY          TIME CMD
29190 pts/1    00:00:00 bash
 5012 pts/1    00:00:00   bash
 5014 pts/1    00:00:00     bash
 5016 pts/1    00:00:00       sleep
 5015 pts/1    00:00:00     sleep
 5013 pts/1    00:00:00   ps

En prenant le | ps -Hmélange juste pour que nous puissions voir les accolades imbriquées, nous pouvons exécuter ps auxf | lessdans un autre shell.

saml     29190  0.0  0.0 117056  3004 pts/1    Ss   13:39   0:00  \_ bash
saml      5191  0.0  0.0 117056  2336 pts/1    S+   14:42   0:00  |   \_ bash
saml      5193  0.0  0.0 107892   512 pts/1    S+   14:42   0:00  |   |   \_ sleep 20
saml      5192  0.0  0.0 107892   508 pts/1    S+   14:42   0:00  |   \_ sleep 20
saml      5068  0.2  0.0 116824  3416 pts/6    Ss   14:42   0:00  \_ bash
saml      5195  0.0  0.0 115020  1272 pts/6    R+   14:42   0:00      \_ ps auxf
saml      5196  0.0  0.0 110244   880 pts/6    S+   14:42   0:00      \_ less

Mais attendez, il y a plus!

Si vous retirez les tuyaux et utilisez cette forme de commande, nous voyons ce que vous attendez réellement:

$ { { { sleep 10; } ; { sleep 10; } ; sleep 10; } } | watch "ps -H"

Maintenant, dans la fenêtre de surveillance résultante, nous obtenons une mise à jour toutes les 2 secondes de ce qui se passe:

Voici le premier sleep 10:

  PID TTY          TIME CMD
29190 pts/1    00:00:00 bash
 5676 pts/1    00:00:00   bash
 5678 pts/1    00:00:00     sleep
 5677 pts/1    00:00:00   watch
 5681 pts/1    00:00:00     watch
 5682 pts/1    00:00:00       ps

Voici la seconde sleep 10:

  PID TTY          TIME CMD
29190 pts/1    00:00:00 bash
 5676 pts/1    00:00:00   bash
 5691 pts/1    00:00:00     sleep
 5677 pts/1    00:00:00   watch
 5694 pts/1    00:00:00     watch
 5695 pts/1    00:00:00       ps

Voici le troisième sleep 10:

  PID TTY          TIME CMD
29190 pts/1    00:00:00 bash
 5676 pts/1    00:00:00   bash
 5704 pts/1    00:00:00     sleep
 5677 pts/1    00:00:00   watch
 5710 pts/1    00:00:00     watch
 5711 pts/1    00:00:00       ps

Notez que les trois dormeurs, bien qu'invoqués à différents niveaux d'imbrication des accolades, ne restent en fait pas dans le PID 5676 de Bash. Je crois donc que votre problème est auto-infligé avec l'utilisation de | ps -H.

Conclusions

L'utilisation de | ps -H(c'est-à-dire le tube) provoque un sous-shell supplémentaire, alors n'utilisez pas cette méthode lorsque vous essayez d'interroger ce qui se passe.

slm
la source
donc, "Seuls les processus stipulés dans le premier niveau des accolades sont exécutés dans le cadre du shell Bash d'origine."
xealits
@xealits - est-ce un suivi Q que vous me demandez?
slm
@slm c'est juste l'accent mis sur le point principal de la réponse, comme je l'ai vu. Les commandes du premier niveau d'accolades exécutées dans le shell actuel, les accolades imbriquées créent de nouveaux shells. Les parenthèses diffèrent dans la création de sous-shell immédiatement, au premier niveau. Si je me trompe - corrigez-moi. Mais, comme d'autres le soulignent, la question initiale a également des pipelines. D'où la création de processus distincts. Et les accolades doivent créer une coque lorsqu'elles sont utilisées pour un processus distinct. C'est probablement la raison du comportement en question.
xealits
maintenant, après avoir relu votre message, je vois que j'avais tort - la déclaration d'imbrication ne concerne que le cas des pipelines. Ainsi, les accolades ne créent jamais de nouveaux coques, à moins que vous n'enveloppiez un processus séparé - alors ils doivent le faire.
xealits
@xealits - c'est exact.
slm
7

Je posterai les résultats de mes tests, ce qui m'amène à conclure que bash crée un sous-shell pour une commande de groupe si et seulement si cela fait partie du pipeline, c'est comme si l'on appelait une fonction qui serait également appelée en sous-coque.

$ { A=1; { A=2; sleep 2; } ; echo $A; }
2

$ { A=1; { A=2; sleep 2; } | sleep 1; echo $A; }
1
Miroslav Koškár
la source
Mon A le montre également.
slm