Quelles portées peuvent avoir les variables de shell?

42

Je viens de rencontrer un problème qui me montre que je ne suis pas clair sur la portée des variables du shell.

J'essayais d'utiliser bundle install, qui est une commande Ruby qui utilise la valeur de $GEM_HOMEpour faire son travail. J'avais défini $GEM_HOME, mais la commande a ignoré cette valeur jusqu'à ce que je l'utilise export, comme dans export GEM_HOME=/some/path.

J'ai lu que cela rend la variable "globale" (ou variable d'environnement ), mais je ne comprends pas ce que cela signifie. Je connais les globals dans la programmation, mais pas parmi les programmes distincts.

De plus, étant donné que ma configuration de telles variables ne s'applique qu'à la session shell en cours, comment pourrais-je les définir pour, par exemple, un processus démonisé?

Quelles portées peuvent avoir les variables de shell?

Nathan Long
la source

Réponses:

33

Les processus sont organisés sous forme d'arborescence: chaque processus a un parent unique, sauf initqu'il PIDest toujours 1 et n'a pas de parent.

La création d'un nouveau processus passe généralement par une paire d' appels fork/ execvsystème, où l'environnement du processus enfant est une copie du processus parent.

Pour placer une variable dans l'environnement à partir du shell, vous devez lui exportaffecter cette variable, de sorte qu'elle soit visible de manière récursive pour tous les enfants. Sachez toutefois que si un enfant modifie la valeur d'une variable, la valeur modifiée ne lui est visible que de même que tous les processus créés après cette modification (une copie , comme indiqué précédemment).

Tenez également compte du fait qu'un processus enfant peut modifier son environnement, par exemple, le réinitialiser aux valeurs par défaut, comme cela est probablement le cas loginpar exemple.

enzotib
la source
1
Ah! OK, voyons si je comprends ça. Dans le shell, si je dis FOO=bar, cela définit la valeur du processus shell en cours. Si je lance ensuite un programme comme ( bundle install), cela crée un processus enfant, auquel il n’a pas accès FOO. Mais si j'avais dit export FOO=bar, le processus enfant (et ses descendants) y auraient accès. L'un d'eux pourrait, à son tour, appeler export FOO=buzzpour changer la valeur pour ses descendants, ou simplement FOO=buzzpour changer la valeur uniquement pour elle-même. Est-ce à peu près correct?
Nathan Long
2
@NathanLong Ce n'est pas tout à fait ça: dans tous les shells modernes, une variable est soit exportée (ainsi toute modification de valeur est reflétée dans l'environnement des descendants) ou non exportée (ce qui signifie que la variable n'est pas dans l'environnement). En particulier, si la variable est déjà dans l'environnement au démarrage du shell, elle est exportée.
Gilles 'SO- arrête d'être méchant'
2
J'étais un peu dérouté par la phrase "si un enfant change la valeur d'une variable, la valeur modifiée n'est visible que par lui et tous les processus créés après ce changement". Il serait plus correct de dire "visible pour lui et tous ses processus descendants créés après ce changement" - les autres enfants du processus parent, même ceux démarrés après le processus enfant, ne sont pas affectés.
Jaan
26

Au moins les valeurs inférieures kshet inférieures bash, les variables peuvent avoir trois portées, et non deux comme le sont actuellement toutes les réponses restantes.

Outre les étendues de variable exportée (c'est-à-dire d'environnement) et de variable non exportée dans le shell, il en existe une troisième plus étroite pour les variables locales de fonction.

Les variables déclarées dans les fonctions du shell avec le typesetjeton ne sont visibles qu'à l'intérieur des fonctions dans lesquelles elles sont déclarées et des (sous-fonctions) appelées à partir de là.

Ce ksh/ bashcode:

# Create a shell script named /tmp/show that displays the scoped variables values.    
echo 'echo [$environment] [$shell] [$local]' > /tmp/show
chmod +x /tmp/show

# Function local variable declaration
function f
{
    typeset local=three
    echo "in function":
    . /tmp/show 
}

# Global variable declaration
export environment=one

# Unexported (i.e. local) variable declaration
shell=two

# Call the function that creates a function local variable and
# display all three variable values from inside the function
f

# Display the three values from outside the function
echo "in shell":
. /tmp/show 

# Display the same values from a subshell
echo "in subshell":
/tmp/show

# Display the same values from a disconnected shell (simulated here by a clean environment start)
echo "in other shell"
env -i /tmp/show 

produit cette sortie:

in function:
[one] [two] [three]
in shell:
[one] [two] []
in subshell:
[one] [] []
in other shell
[] [] []

Comme vous pouvez le constater, la variable exportée est affichée à partir des trois premiers emplacements, les variables non exportées ne sont pas affichées en dehors du shell actuel et la variable locale de la fonction n'a aucune valeur en dehors de la fonction elle-même. Le dernier test n'affiche aucune valeur, car les variables exportées ne sont pas partagées entre les shells, c'est-à-dire qu'elles peuvent uniquement être héritées et que la valeur héritée ne peut plus être affectée par le shell parent.

Notez que ce dernier comportement est assez différent de celui de Windows dans lequel vous pouvez utiliser des variables système entièrement globales et partagées par tous les processus.

jlliagre
la source
12

Ils sont étendus par processus

Les autres intervenants m'ont aidé à comprendre que la portée des variables shell concerne les processus et leurs descendants .

Lorsque vous tapez une commande comme lssur la ligne de commande, vous êtes en train de forger un processus pour exécuter le lsprogramme. Le nouveau processus a votre shell comme parent.

Tout processus peut avoir ses propres variables "locales", qui ne sont pas transmises aux processus enfants. Il peut également définir des variables "d'environnement", qui sont. Utiliser exportcrée une variable d'environnement. Dans les deux cas, les processus indépendants (homologues de l'original) ne verront pas la variable. nous ne contrôlons que ce que les processus enfants voient.

Supposons que vous ayez un shell bash, que nous appellerons A. Vous tapez bash, ce qui crée un shell bash de processus enfant, que nous appellerons B. Tout ce que vous avez appelé exporten A sera toujours défini en B.

Maintenant, en B, vous dites FOO=b. Une des deux choses va se passer:

  • Si B n'a pas reçu (de A) une variable d'environnement appelée FOO, il créera une variable locale. Les enfants de B ne l’auront pas (à moins que B n’appelle export).
  • Si B a effectivement reçu (de A) une variable d’environnement appelée FOO, il la modifiera pour elle-même et ses enfants ensuite créés . Les enfants de B verront la valeur attribuée par B. Cependant, cela n'affectera pas du tout A.

Voici une démo rapide.

FOO=a      # set "local" environment variable
echo $FOO  # 'a'
bash       # forks a child process for the new shell
echo $FOO  # not set
exit       # return to original shell
echo $FOO  # still 'a'

export FOO # make FOO an environment variable
bash       # fork a new "child" shell
echo $FOO  # outputs 'a'
FOO=b      # modifies environment (not local) variable
bash       # fork "grandchild" shell
echo $FOO  # outputs 'b'
exit       # back to child shell
exit       # back to original shell
echo $FOO  # outputs 'a'

Tout cela explique mon problème initial: je me suis installé GEM_HOMEdans mon shell, mais lorsque j'ai appelé bundle install, cela a créé un processus enfant. Parce que je n'avais pas utilisé export, le processus enfant n'a pas reçu le shell GEM_HOME.

Exportation

Vous pouvez "dés-exporter" une variable - en l'empêchant d'être transmise aux enfants - en utilisant export -n FOO.

export FOO=a   # Set environment variable
bash           # fork a shell
echo $FOO      # outputs 'a'
export -n FOO  # remove environment var for children
bash           # fork a shell
echo $FOO      # Not set
exit           # back up a level
echo $FOO      # outputs 'a' - still a local variable
Nathan Long
la source
1
Lorsque vous dites "cela le modifiera pour lui-même et ses enfants", vous devez préciser que seuls les enfants créés après la modification verront la valeur modifiée.
enzotib
1
@enzotib - bon point. Mis à jour.
Nathan Long
3

La meilleure explication que je puisse trouver sur l'exportation est celle-ci:

http://tldp.org/LDP/Bash-Beginners-Guide/html/sect_03_02.html

La variable définie dans un sous-shell ou un shell enfant est uniquement visible par le sous-shell dans lequel elle est définie. La variable exportée est réellement transformée en une variable d’environnement. Donc, pour être clair, vous bundle installexécutez son propre shell qui ne le voit pas $GEM_HOMEsauf si une environmentvariable est également exportée.

Vous pouvez consulter la documentation sur la portée variable ici:

http://www.tldp.org/LDP/abs/html/subshells.html

Karlson
la source
Ah, donc j’ai eu tort d’utiliser le terme "variable d’environnement" pour FOO=bar; vous devez utiliser exportpour en faire un. Question corrigée en conséquence.
Nathan Long
Jetez un coup d'œil au lien que j'ai ajouté.
Karlson
3

Il existe une hiérarchie d'étendues variables, comme prévu.

Environnement

L'envergure la plus externe est l'environnement. Il s'agit du seul domaine géré par le système d'exploitation. Il est donc garanti qu'il existe pour chaque processus. Lorsqu'un processus est démarré, il reçoit une copie de l'environnement de son parent, après quoi les deux deviennent indépendants: la modification de l'environnement de l'enfant ne modifie pas celui du parent, et la modification de l'environnement du parent ne modifie pas celui d'un enfant existant.

Variables shell

Les coquilles ont leur propre notion de variables. C’est là que les choses commencent à devenir un peu déroutantes.

Lorsque vous affectez une valeur à une variable dans un shell et que cette variable existe déjà dans l'environnement, la variable d'environnement reçoit la nouvelle valeur. Toutefois, si la variable ne figure pas encore dans l'environnement, elle devient une variable shell . Les variables shell n'existent que dans le processus shell, de la même manière que les variables Ruby n'existent que dans un script Ruby. Ils ne sont jamais hérités par les processus enfants.

Voici où le exportmot clé entre en jeu. Il copie une variable shell dans l'environnement du processus shell, ce qui permet aux processus enfants d'hériter.

Variables locales

Les variables locales sont des variables shell étendues aux blocs de code les contenant. Vous déclarez des variables locales avec le typesetmot - clé (portable) ou localou declare(Bash). Comme pour les autres variables du shell, les processus enfants n'héritent pas des variables locales. De plus, les variables locales ne peuvent pas être exportées.

Serpent
la source