web-dev-qa-db-fra.com

Portée des variables locales dans les fonctions shell

Après avoir lu 24.2. Variables locales , j'ai pensé que déclarer une variable var avec le mot clé local signifiait que la valeur de var n'était accessible que dans le bloc de code délimité par les accolades d'une fonction.

Cependant, après avoir exécuté l'exemple suivant, j'ai découvert que var peut également être consulté, lu et écrit à partir des fonctions invoquées par ce bloc de code - c'est-à-dire même si var est déclaré local to outerFunc, innerFunc est toujours 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}]"

Production:

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 bien le comportement attendu.

31
maddouri

Les variables shell ont un 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 syntaxe standard function_name () { … }, alors 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, donc elle n'est pas visible dans les autres fonctions appelées par cela.

  2. le zsh/private le plugin autoloadable dans zsh fournit un mot clé/intégré private qui peut être utilisé pour déclarer une variable à portée statique.

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

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.

6
afbach

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]

Source

2
Steven Penny

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

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

Étant donné que innerFunc() a été appelée en tant qu'enfant de outerFunc(), var se trouve 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 value Dans function innerFunc().

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

2
Joseph Tingiris