Pourquoi une variable est-elle visible dans un sous-shell?

18

Le Learning Bash Book mentionne qu'un sous-shell héritera uniquement des variables d'environnement et des descripteurs de fichiers, etc., et qu'il n'héritera pas des variables qui ne sont pas exportées:

$ var=15
$ (echo $var)
15
$ ./file # this file include the same command echo $var

$

Comme je sais, le shell créera deux sous-coquilles pour ()et pour ./file, mais pourquoi dans le ()cas la sous-coquille identifie-t-elle la varvariable bien qu'elle ne soit pas exportée et dans le ./filecas où elle ne l'a pas identifiée?

# Strace for () 
clone(child_stack=0, flags=CLONE_CHILD_CLEARTID|CLONE_CHILD_SETTID|SIGCHLD, child_tidptr=0x7f24558b1a10) = 25617
# Strace for ./file
clone(child_stack=0, flags=CLONE_CHILD_CLEARTID|CLONE_CHILD_SETTID|SIGCHLD, child_tidptr=0x7f24558b1a10) = 25631

J'ai essayé d'utiliser stracepour comprendre comment cela se produit et, de façon surprenante, j'ai trouvé que bash utilisera les mêmes arguments pour l'appel système clone, ce qui signifie que le processus forké ()et ./filedevrait avoir le même espace d'adressage de processus du parent, alors pourquoi dans le ()cas, la variable est visible pour le sous-shell et la même chose ne se produit pas pour le ./filecas, bien que les mêmes arguments soient basés sur l'appel système du clone?

user3718463
la source
vinc17 dire vrai, même si vous obtenez pstree lorsque vous avez sous-shell, vous croyez cette question.
PersianGulf

Réponses:

15

Le Learning Bash Book est faux. Les sous-coques héritent de toutes les variables. Même $$(le PID de la coque d'origine) est conservé. La raison en est que pour un sous-shell, le shell ne fait que forger et n'exécute pas un nouveau shell (au contraire, lorsque vous tapez ./file, une nouvelle commande est exécutée, par exemple un nouveau shell; dans la sortie strace, regardez execveet similaire) . Donc, en gros, c'est juste une copie (avec quelques différences documentées).

Remarque: ce n'est pas spécifique à bash; cela est vrai pour n'importe quel shell.

vinc17
la source
Oky mais j'ai essayé maintenant de lancer strace sur le shell et j'ai essayé d'exécuter ./file mais je ne trouve aucun appel à exec et donc l'espace d'adressage devrait être le même pour les deux processus alors comment cela peut-il être expliqué?
user3718463
@ user3718463 Avez-vous également utilisé l' -foption de suivi des straceenfants? C'est nécessaire pour trouver les exécutifs.
vinc17
yup je le comprends merci beaucoup, il me manquait l'option -f, et donc je ne trouve pas l'appel exec sys
user3718463
16

Soit vous, soit le livre confondez un sous-shell avec un sous-processus qui est un shell.

Certaines constructions de shell entraînent le shell à forger un processus enfant. Sous Linux, forkest un cas particulier de l' cloneappel système plus général , que vous avez observé dans le stracejournal. L'enfant exécute une partie du script shell. Le processus enfant est appelé sous - shell . La construction la plus directe est command1 &: command1s'exécute dans un sous-shell et les commandes suivantes s'exécutent dans le shell parent. D'autres constructions qui créent un sous-shell incluent la substitution de commandes $(command2)et les canaux command3 | command4( command3s'exécute dans un sous-shell, command4s'exécute dans un sous-shell dans la plupart des shells mais pas dans ksh ou zsh).

Un sous-shell est une copie du processus parent, il a donc non seulement les mêmes variables d'environnement, mais aussi toutes les mêmes définitions internes: variables (y compris $$, l'ID de processus du processus shell d'origine), fonctions, alias, options, etc. Avant d'exécuter le code dans le sous-shell, bash définit la variable BASHPIDsur l'ID de processus du processus enfant.

Lorsque vous exécutez ./file, cela exécute une commande externe. Tout d'abord, le shell lance un processus enfant; puis ce processus enfant exécute (avec l' execveappel système) le fichier exécutable ./file. Un processus enfant hérite des attributs de processus de ses parents: environnement, répertoire courant, etc. Les aspects internes de l'application sont perdus dans l' execveappel: les variables, fonctions, etc. non exportées sont des notions bash que le noyau ne connaît pas, et ils sont perdus lorsque bash exécute un autre programme. Même si cet autre programme se trouve être un script bash, il est exécuté par une nouvelle instance de bash qui ne sait pas ou ne se soucie pas que son processus parent se trouve être également une instance de bash. Ainsi, une variable shell (variable non exportée) ne survit pas execve.

Gilles 'SO- arrête d'être méchant'
la source
Cette réponse m'a éclairé pas mal de choses. La seule chose que je ne comprends pas est cette phrase dans le deuxième paragraphe: "L'enfant exécute une partie du script shell." De quel script shell s'agit-il?
flow2k
@ flow2k Le script (ie le programme) que le shell interprète.
Gilles 'SO- arrête d'être méchant'