Règle pour invoquer un sous-shell dans Bash?

24

Je semble mal comprendre la règle Bash pour créer un sous-shell. Je pensais que les parenthèses créent toujours un sous-shell, qui fonctionne comme son propre processus.

Cependant, cela ne semble pas être le cas. Dans l'extrait de code A (ci-dessous), la deuxième sleepcommande ne s'exécute pas dans un shell séparé (comme déterminé par pstreedans un autre terminal). Cependant, dans le Code Snippet B, la deuxième sleepcommande ne fonctionne dans une coque séparée. La seule différence entre les extraits est que le deuxième extrait a deux commandes entre parenthèses.

Quelqu'un pourrait-il expliquer la règle de création des sous-coquilles?

CODE SNIPPET A:

sleep 5
(
sleep 5
)

CODE SNIPPET B:

sleep 5
(
x=1
sleep 5
)
timide
la source

Réponses:

20

Les parenthèses démarrent toujours un sous-shell. Ce qui se passe, c'est que bash détecte que sleep 5c'est la dernière commande exécutée par ce sous-shell, donc il appelle à la execplace de fork+ exec. La sleepcommande remplace le sous-shell dans le même processus.

En d'autres termes, le scénario de base est:

  1. ( … )créer un sous-shell. Le processus d'origine appelle forket wait. Dans le sous-processus, qui est un sous-shell:
    1. sleepest une commande externe qui nécessite un sous-processus du sous-processus. Le sous-shell appelle forket wait. Dans le sous-processus:
      1. Le sous-processus exécute la commande externe → exec.
      2. Finalement, la commande se termine → exit.
    2. wait se termine dans le sous-shell.
  2. wait se termine dans le processus d'origine.

L'optimisation est:

  1. ( … )créer un sous-shell. Le processus d'origine appelle forket wait. Dans le sous-processus, qui est un sous-shell jusqu'à ce qu'il appelle exec:
    1. sleep est une commande externe, et c'est la dernière chose que ce processus doit faire.
    2. Le sous-processus exécute la commande externe → exec.
    3. Finalement, la commande se termine → exit.
  2. wait se termine dans le processus d'origine.

Lorsque vous ajoutez quelque chose d'autre après l'appel sleep, le sous-shell doit être conservé, donc cette optimisation ne peut pas se produire.

Lorsque vous ajoutez quelque chose d'autre avant l'appel à sleep, l'optimisation peut être effectuée (et ksh le fait), mais bash ne le fait pas (c'est très conservateur avec cette optimisation).

Gilles 'SO- arrête d'être méchant'
la source
La sous-coque est créée en appelant forket le processus enfant est créé (pour exécuter des commandes externes) en appelantfork + exec . Mais votre premier paragraphe suggère que cela fork + execest également nécessaire pour le sous-shell. Qu'est-ce que je me trompe ici?
haccks
1
@haccks fork+ execn'est pas appelé pour le sous-shell, il est appelé pour la commande externe. Sans aucune optimisation, il y a un forkappel pour le sous-shell et un autre pour la commande externe. J'ai ajouté une description détaillée du flux à ma réponse.
Gilles 'SO- arrête d'être méchant'
Merci beaucoup pour la mise à jour. Maintenant ça explique mieux. Je peux en déduire qu'en cas de (...)(dans le cas de base), il peut y avoir ou non un appel à execdépend si le sous-shell a une commande externe à exécuter, alors qu'en cas d'exécution d'une commande externe, il doit y en avoir fork + exec.
haccks
Une dernière question: cette optimisation fonctionne-t-elle uniquement pour le sous-shell ou peut-elle être effectuée pour une commande comme datedans un shell?
haccks
@haccks Je ne comprends pas la question. Cette optimisation consiste à invoquer une commande externe comme la dernière chose qu'un processus shell fait. Il ne se limite pas aux sous-coquilles: comparez strace -f -e clone,execve,write bash -c 'date'etstrace -f -e clone,execve,write bash -c 'date; true'
Gilles 'SO- arrête d'être méchant'
4

Dans le guide de programmation avancé de Bash :

"En général, une commande externe dans un script se dérobe à un sous-processus, contrairement à une commande intégrée Bash. Pour cette raison, les commandes intégrées s'exécutent plus rapidement et utilisent moins de ressources système que leurs équivalents de commandes externes."

Et un peu plus loin:

"Une liste de commandes intégrée entre parenthèses s'exécute en tant que sous-shell."

Exemples:

[root@talara test]# echo $BASHPID
10792
[root@talara test]# (echo $BASHPID)
4087
[root@talara test]# (echo $BASHPID)
4088
[root@talara test]# (echo $BASHPID)
4089

Exemple d'utilisation du code OP (avec des périodes de sommeil plus courtes car je suis impatient):

echo $BASHPID

sleep 2
(
    echo $BASHPID
    sleep 2
    echo $BASHPID
)

Le résultat:

[root@talara test]# bash sub_bash
6606
6608
6608
Tim
la source
2
Merci pour la réponse Tim. Je ne suis pas sûr que cela réponde pleinement à ma question. Depuis "Une liste de commandes incorporée entre parenthèses s'exécute comme un sous-shell", je m'attendrais à ce que le second sleeps'exécute dans un sous-shell (peut-être sur le processus du sous-shell car il est intégré, plutôt qu'un sous-processus du sous-shell). Cependant, dans tous les cas, je m'attendais à ce qu'un sous-shell existe, c'est-à-dire un sous-processus Bash sous le processus Bash parent. Pour l'extrait B ci-dessus, cela ne semble pas être le cas.
timide
Correction: Parce sleepqu'il ne semble pas être intégré, je m'attendrais à ce que le deuxième sleepappel dans les deux extraits s'exécute dans un sous-processus du processus de sous-shell.
timide
@bashful J'ai pris la liberté de pirater votre code avec ma $BASHPIDvariable. Malheureusement, la façon dont vous le faisiez ne vous donnait pas toute l'histoire, je crois. Voir ma sortie ajoutée dans la réponse.
Tim
4

Une note supplémentaire à la réponse @Gilles.

Comme l'a dit Gilles: The parentheses always start a subshell.

Cependant, les chiffres que possède ce sous-shell peuvent se répéter:

$ (echo "$BASHPID and $$"; sleep 1)
2033 and 31679
$ (echo "$BASHPID and $$"; sleep 1)
2040 and 31679
$ (echo "$BASHPID and $$"; sleep 1)
2047 and 31679

Comme vous pouvez le voir, le $$ continue de se répéter, et c'est comme prévu, car (exécutez cette commande pour trouver la bonne man bashligne):

$ LESS=+/'^ *BASHPID' man bash

BASHPID Développe
l'ID de processus du processus bash en cours. Cela diffère de $$ dans certaines circonstances, telles que les sous-coquilles qui ne nécessitent pas la réinitialisation de bash.

C'est-à-dire: si le shell n'est pas réinitialisé, le $$ est le même.

Ou avec ça:

$ LESS=+/'^ *Special Parameters' man bash

Paramètres spéciaux
$ Développe l'ID de processus du shell. Dans un sous-shell (), il se développe à l'ID de processus du shell actuel, pas le sous-shell.

Le $$est l'ID de l'enveloppe actuelle ( et non le sous - shell).


la source
1
Belle astuce pour ouvrir la page de manuel bash dans une section spécifique
Daniel Serodio