Les parenthèses placent-elles vraiment la commande dans un sous-shell?

94

D'après ce que j'ai lu, mettre une commande entre parenthèses devrait l'exécuter dans un sous-shell, comme si vous utilisiez un script. Si cela est vrai, comment voit-il la variable x si x n'est pas exporté?

x=1

Courir (echo $x)sur la ligne de commande a pour résultat 1

L'exécution echo $xd'un script ne donne rien, comme prévu

Igorio
la source

Réponses:

134

Un sous-shell commence par être une copie presque identique du processus shell original. Sous le capot, le shell appelle l' forkappel système 1 qui crée un nouveau processus dont le code et la mémoire sont des copies 2 . Lorsque le sous-shell est créé, il existe très peu de différences entre celui-ci et son parent. En particulier, ils ont les mêmes variables. Même la $$variable spéciale conserve la même valeur dans les sous-shell: il s'agit de l'ID de processus du shell d'origine. De même $PPIDest le PID du parent du shell d'origine.

Quelques coquilles changent quelques variables dans le sous-coquille. Bash définit BASHPIDle PID du processus shell, qui change de sous-shell. Bash, zsh et mksh font en sorte $RANDOMde générer différentes valeurs dans le parent et dans le sous-shell. Mais à part les cas spéciaux intégrés tels que ceux-ci, toutes les variables ont la même valeur dans le sous-shell que dans le shell d'origine, le même statut d'exportation, le même statut en lecture seule, etc. Toutes les définitions de fonctions, définitions d'alias, options de shell et les autres paramètres sont également hérités.

Un sous-shell créé par (…)a les mêmes descripteurs de fichier que son créateur. Certains autres moyens de créer des sous-shell modifient certains descripteurs de fichier avant l'exécution du code utilisateur; Par exemple, le côté gauche d'un tuyau se trouve dans un sous-shell 3 avec une sortie standard connectée au tuyau. Le sous-shell commence également avec le même répertoire actuel, le même masque de signal, etc. Une des rares exceptions est que les sous-shell n'héritent pas des traps personnalisés: les signaux ignorés ( ) restent ignorés dans le sous-shell, mais les autres traps ( SIGNAL ) sont réinitialisés. à l'action par défaut 4 .trap '' SIGNALtrap CODE

Un sous-shell est donc différent de l'exécution d'un script. Un script est un programme séparé. Ce programme séparé peut également être un script exécuté par le même interprète que le parent, mais cette coïncidence ne confère au programme séparé aucune visibilité particulière sur les données internes du parent. Les variables non exportées sont des données internes. Ainsi, lorsque l'interpréteur du script de shell enfant est exécuté , il ne voit pas ces variables. Les variables exportées, c'est-à-dire les variables d'environnement, sont transmises aux programmes exécutés.

Ainsi:

x=1
(echo $x)

imprime 1parce que le sous-shell est une réplication du shell qui l’a engendré.

x=1
sh -c 'echo $x'

arrive à exécuter un shell en tant que processus enfant d'un shell, mais xla seconde ligne n'a pas plus de connexion avec xla seconde ligne que dans

x=1
perl -le 'print $x'

ou

x=1
python -c 'print x'

1 Une exception est le ksh93shell où le forgeage est optimisé et où la plupart de ses effets secondaires sont imités.
2 Sémantiquement, ce sont des copies. Du point de vue de la mise en œuvre, il y a beaucoup de partage.
3 Pour le côté droit, cela dépend de la coque.
4 Si vous testez cela, notez que des choses similaires$(trap) peuvent signaler les pièges du shell original. Notez également que beaucoup de coquilles ont des bugs dans les cas de coin impliquant des pièges. Par exemple, ninjalj note que depuis bash 4.3, bash -x -c 'trap "echo ERR at \$BASH_SUBSHELL \$BASHPID" ERR; set -E; false; echo one subshell; (false); echo two subshells; ( (false) )'exécute le ERRpiège à partir du sous-shell imbriqué dans le cas «deux sous-réservoirs», mais pas le ERRpiège du sous-shell intermédiaire - l' set -Eoption devrait propager leERRpiège à tous les sous-shell, mais le sous-shell intermédiaire est optimisé et n’est donc pas là pour exécuter son ERRpiège.

Gilles
la source
2
@Kusalananda No. ( x=out; (x=in; echo $x))
Gilles
2
@ flow2k C'est l'ordre d'expansion pour ce qui se passe au même niveau. Mais vous devez également prendre en compte la manière dont l'expansion est associée à l'évaluation. Lorsque l'expansion nécessite l'évaluation d'une construction imbriquée, la construction interne est évaluée en premier. Ainsi, par exemple, pour évaluer echo $(x=2; echo $x), le fragment $(x=2; echo $x)doit être développé. Cela nécessite d'évaluer la commande x=2; echo $x. L'expansion de $xse produit pendant cette évaluation, après avoir évalué la pièce x=2.
Gilles
2
@ flow2k Il n'y a pas d'ordre entre le développement des paramètres et la substitution de commandes. Notez que cette phrase utilise des points-virgules pour séparer les étapes de développement, mais que le développement des paramètres et la substitution de commande se trouvent dans la même clause délimitée par des points-virgules (oui, c'est subtil). L'ordre est important lorsque l'une des parties a un effet secondaire qui affecte l'autre partie, par exemple (avec xnon défini) echo $(echo foo >somefile)${x-$(cat somefile)}ou echo $(echo $x),${x=1}.
Gilles
1
@Gilles; Je suis confus. Si un sous-shell est différent de l'exécution d'un script, pourquoi est-il dit que: L' exécution d'un script shell lance un nouveau processus, un sous-shell. ? En outre, un environnement de sous-shell doit être créé en tant que duplicata de l'environnement du shell . Par conséquent, ./file sera exécuté dans un environnement de sous-shell et il devrait donc hériter des paramètres de shell définis par une affectation de variable.
haccks
2
@haccks La définition dans l'ABS est approximative et pas très bonne. Les exemples sont bons, mais les deux premières lignes de cette page sont tellement simplifiées qu'elles sont fausses. L'exécution d'un script à partir d'un autre script lance un nouveau processus qui n'est pas un sous-shell. Dans le SUS, les définitions sont correctes (mais pas toujours très faciles à comprendre). ./filen'est pas exécuté dans un sous-shell. Voir aussi unix.stackexchange.com/q/261638 et unix.stackexchange.com/a/157962
Gilles
15

Évidemment, comme le dit toute la documentation, une commande entre parenthèses est exécutée dans un sous-shell.

Le sous-shell hérite d'une copie de toutes les variables du parent. La différence est que les modifications que vous apportez dans le sous-shell ne sont pas également effectuées dans le parent.

La page de manuel ksh le rend un peu plus clair que celui de bash:

man ksh:

Une commande entre parenthèses est exécutée dans un sous-shell sans supprimer les variables non exportées.

man bash:

(liste)

list est exécutée dans un environnement de sous-shell (voir ENVIRONNEMENT D'EXÉCUTION DE COMMANDE ci-dessous). Les affectations de variables et les commandes intégrées qui affectent l'environnement du shell ne restent pas en vigueur une fois la commande terminée.

ENVIRONNEMENT D'EXECUTION DE COMMANDEMENT

Le shell a un environnement d'exécution composé des éléments suivants: [...] paramètres du shell définis par une affectation de variable [...].
La substitution de commande, les commandes groupées avec des parenthèses et les commandes asynchrones sont appelées dans un environnement de sous-shell qui est une copie de l'environnement du shell, [...]

Mikel
la source
3
Cela doit être comparé à When a simple command other than a builtin or shell function is to be executed, it is invoked in a separate execution environment that consists of the following., qui contient l'élément: · shell variables and functions marked for export, along with variables exported for the command, passed in the environment(de la même man bashsection) ce qui explique pourquoi un echo $xscript n'imprime rien s'il xn'est pas exporté.
Johan E