Portée des variables locales dans les fonctions shell

28

Après avoir lu 24.2. Variables locales , je pensais que déclarer une variable varavec le mot-clé localsignifiait que sa varvaleur n'était accessible que dans le bloc de code délimité par les accolades d'une fonction.

Cependant, après l' exécution de l'exemple suivant, j'ai découvert que varpeut également être consulté, lu et écrit des fonctions invoquées par ce bloc de code - à savoir même si varest déclarée localà outerFunc, innerFuncest encore capable de le lire et de modifier sa valeur.

Run It Online

#!/usr/bin/env bash

function innerFunc() {
    var='new value'
    echo "innerFunc:                   [var:${var}]"
}

function outerFunc() {
    local var='initial value'

    echo "outerFunc: before innerFunc: [var:${var}]"
    innerFunc
    echo "outerFunc: after  innerFunc: [var:${var}]"
}

echo "global:    before outerFunc: [var:${var}]"
outerFunc
echo "global:    after  outerFunc: [var:${var}]"

Sortie:

global:    before outerFunc: [var:]               # as expected, `var` is not accessible outside of `outerFunc`
outerFunc: before innerFunc: [var:initial value]
innerFunc:                   [var:new value]      # `innerFunc` has access to `var` ??
outerFunc: after  innerFunc: [var:new value]      # the modification of `var` by `innerFunc` is visible to `outerFunc` ??
global:    after  outerFunc: [var:]

Q: Est-ce un bug dans mon shell (bash 4.3.42, Ubuntu 16.04, 64bit) ou est-ce le comportement attendu?

EDIT: Résolu. Comme l'a noté @MarkPlotnick, c'est en effet le comportement attendu.

maddouri
la source
C'est le comportement attendu
fpmurphy
2
Suis-je le seul à penser que c'est bizarre que sur la dernière ligne de sortie la valeur de varsoit vide? varest défini globalement innerFunc, alors pourquoi ne reste-t-il pas jusqu'à la fin du script?
Harold Fischer

Réponses:

22

Les variables shell ont une portée dynamique . Si une variable est déclarée comme locale pour une fonction, cette étendue reste jusqu'à ce que la fonction retourne.

Il existe deux exceptions:

  1. dans ksh93, si une fonction est définie avec la function_name () { … }syntaxe standard , ses variables locales obéissent à la portée dynamique. Mais si une fonction est définie avec la syntaxe ksh, function function_name { … }alors sa variable locale obéit à la portée lexicale / statique, de sorte qu'elles ne sont pas visibles dans les autres fonctions appelées par cela.

  2. le zsh/privateplugin autochargeable zshfournit un privatemot-clé / intégré qui peut être utilisé pour déclarer une variable à portée statique.

frêne, bash, pdksh et dérivés, bosh n'a qu'une portée dynamique.

Gilles 'SO- arrête d'être méchant'
la source
Toutes les variables du shell ont-elles une portée dynamique ou cela ne s'applique-t-il qu'aux variables déclarées avec local?
Harold Fischer
@HaroldFischer Toutes les variables ont une portée dynamique. Avec une déclaration typesetou declareou local, la portée est jusqu'à ce que la fonction retourne. Sans une telle déclaration, la portée est globale.
Gilles 'SO- arrête d'être méchant'
6

Ce n'est pas un bug, l'appel dans le contexte de externalFunc utilise cette copie locale de $ var. Le "local" dans externalFunc signifie que le global n'est pas modifié. Si vous appelez innerFunc en dehors de externalFunc, alors il y aura un changement au $ var global, mais pas au $ var local du externalFunc. Si vous avez ajouté "local" à innerFunc, alors $ var de externalFunc ne serait pas modifié - en substance, il y en aurait 3:

  • $ global :: var
  • $ externalFunc :: var
  • $ innerFunc :: var

pour utiliser le format d'espace de noms de Perl, en quelque sorte.

afbach
la source
2

Vous pouvez utiliser une fonction pour forcer la portée locale:

sh_local() {
  eval "$(set)" command eval '\"\$@\"'
}

Exemple:

x() {
  z='new value'
  printf 'function x, z = [%s]\n' "$z"
}
y() {
  z='initial value'
  printf 'function y before x, z = [%s]\n' "$z"
  sh_local x
  printf 'function y after x, z = [%s]\n' "$z"
}
printf 'global before y, z = [%s]\n' "$z"
y
printf 'global after y, z = [%s]\n' "$z"

Résultat:

global before y, z = []
function y before x, z = [initial value]
function x, z = [new value]
function y after x, z = [initial value]
global after y, z = [initial value]

La source

Steven Penny
la source
2

Dans function innerFunc()le var='new value'n'a pas été déclaré comme local , il est donc disponible dans la portée visible (une fois que la fonction a été appelée).

Inversement, dans function outerFunc()le a local var='initial value'été déclaré local , il n'est donc pas disponible dans la portée globale (même si la fonction a été appelée).

Parce qu'il a innerFunc()été appelé en tant qu'enfant de outerFunc(), var est dans la portée locale de outerFunc().

man 1 bash peut aider à clarifier

local [option] [nom [= valeur] ...]

Pour chaque argument, une variable locale nommée nom est créée et une valeur affectée. L'option peut être l'une des options acceptées par declare. Lorsque local est utilisé dans une fonction, le nom de la variable a une portée visible limitée à cette fonction et à ses enfants. ...

Le comportement implicite attendu dans la description peut être obtenu en déclarant local var='new valuedans function innerFunc().

Comme d'autres l'ont dit, ce n'est pas un bogue dans le shell bash. Tout fonctionne comme il se doit.

Joseph Tingiris
la source
Votre première déclaration contredit ce que voit l'utilisateur. Impression de la valeur vardans la portée globale, après avoir appelé à innerFunctravers outFunc, n'imprime pas new value.
Kusalananda