web-dev-qa-db-fra.com

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

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

x=1

Fonctionnement (echo $x) sur la ligne de commande donne 1

Fonctionnement echo $x dans un script ne donne rien, comme prévu

107
Igorio

Un sous-shell commence comme une copie presque identique du processus Shell d'origine. Sous le capot, le Shell appelle l'appel système fork1, qui crée un nouveau processus dont le code et la mémoire sont des copies2. Lorsque le sous-shell est créé, il y a très peu de différences entre lui 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-coquilles: c'est l'ID de processus du Shell d'origine. De même $PPID Est le PID du parent du shell d'origine.

Quelques coquilles changent quelques variables dans la sous-coquille. Bash définit BASHPID sur le PID du processus Shell, qui change en sous-shell. Bash, zsh et mksh font en sorte que $RANDOM Produise des valeurs différentes dans le parent et dans le sous-shell. Mais à l'exception des cas spéciaux intégrés comme 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 de lecture seule, etc. Toutes les définitions de fonction, définitions d'alias, options de shell et d'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-coquilles modifient certains descripteurs de fichiers avant d'exécuter le code utilisateur; par exemple, le côté gauche d'un tuyau passe en sous-coque3 avec sortie standard connectée au tuyau. Le sous-shell commence également avec le même répertoire en cours, le même masque de signal, etc. L'une des rares exceptions est que les sous-shell n'héritent pas de traps personnalisés: les signaux ignorés (trap '' SIGNAL) Restent ignorés dans le sous-shell, mais d'autres les pièges (trap CODE [~ # ~] signal [~ # ~] ) sont réinitialisés à l'action par défaut4.

Un sous-shell est donc différent de l'exécution d'un script. Un script est un programme distinct. Ce programme séparé pourrait également être un script exécuté par le même interprète que le parent, mais cette coïncidence ne donne pas au programme séparé une visibilité spéciale sur les données internes du parent. Les variables non exportées sont des données internes, donc lorsque l'interpréteur du script enfant Shell 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.

Donc:

x=1
(echo $x)

affiche 1 car 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 le x sur la deuxième ligne n'a pas plus de connexion avec le x sur la deuxième ligne que dans

x=1
Perl -le 'print $x'

ou

x=1
python -c 'print x'

1Une exception est le shell ksh93 Où la fourche est optimisée et la plupart de ses effets secondaires sont émulés.
2Sémantiquement, ce sont des copies. Du point de vue de la mise en œuvre, il y a beaucoup de partage en cours.
3Pour le côté droit, cela dépend du Shell.
4Si vous testez cela, notez que des choses comme $(trap) peuvent signaler les pièges du shell d'origine. Notez également que de nombreux obus ont des bogues dans les cas d'angle impliquant des pièges. Par exemple ninjalj note qu'à partir de 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 piège ERR à partir du sous-shell imbriqué dans le cas "deux sous-coques", mais pas le ERR trap du sous-shell intermédiaire - L'option set -E devrait propager le piège ERR à tous les sous-shells mais le sous-shell intermédiaire est optimisé et n'est donc pas là pour exécuter son ERR prendre au piège.

De toute évidence, oui, 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 toutes les modifications que vous apportez dans le sous-shell ne sont pas également apportées dans le parent.

La page de manuel de ksh rend cela 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 )

la liste 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'EXÉCUTION DES COMMANDES

Le Shell possède un environnement d'exécution, qui se compose des éléments suivants: [...] Paramètres du Shell qui sont définis par l'affectation des variables [...].
La substitution de commandes, les commandes regroupées entre parenthèses et les commandes asynchrones sont appelées dans un environnement de sous-shell qui est un doublon de l'environnement Shell, [...]

15
Mikel