Alcance de las variables locales en las funciones de Shell

28

Después de leer 24.2. Variables locales , pensé que declarar una variable varcon la palabra clave localsignificaba que varsolo se podía acceder al valor dentro del bloque de código delimitado por las llaves de una función.

Sin embargo, después de ejecutar el siguiente ejemplo, descubrí que vartambién se puede acceder, leídos y escritos desde las funciones invocadas por ese bloque de código - es decir, a pesar de que varse declara locala outerFunc, innerFunctodavía es capaz de leer y alterar su valor.

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}]"

Salida:

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:]

P: ¿Es un error en mi shell (bash 4.3.42, Ubuntu 16.04, 64 bits) o es el comportamiento esperado?

EDITAR: Resuelto. Como señaló @MarkPlotnick, este es de hecho el comportamiento esperado.

maddouri
fuente
Es el comportamiento esperado
fpmurphy
2
¿Soy el único que piensa que es extraño que en la última línea de salida el valor de varesté vacío? varestá configurado globalmente innerFunc, entonces ¿por qué no se mantiene hasta el final del guión?
Harold Fischer

Respuestas:

22

Las variables de shell tienen un alcance dinámico . Si una variable se declara como local para una función, ese alcance permanece hasta que la función regrese.

Hay dos excepciones:

  1. en ksh93, si una función se define con la function_name () { … }sintaxis estándar , entonces sus variables locales obedecen al alcance dinámico. Pero si una función se define con la sintaxis ksh, function function_name { … }entonces su variable local obedece al ámbito léxico / estático, por lo que no son visibles en otras funciones llamadas por esto.

  2. El zsh/privatecomplemento autocargable en zshproporciona una privatepalabra clave / incorporado que se puede utilizar para declarar una variable con alcance estático.

ash, bash, pdksh y derivados, bosh solo tienen alcance dinámico.

Gilles 'SO- deja de ser malvado'
fuente
¿Todas las variables en el shell tienen un alcance dinámico o esto solo se aplica a las variables declaradas con local?
Harold Fischer
@HaroldFischer Todas las variables tienen un alcance dinámico. Con una typeseto declareo localdeclaración, el alcance es hasta que se devuelve la función. Sin tal declaración, el alcance es global.
Gilles 'SO- deja de ser malvado'
6

No es un error, la llamada dentro del contexto del externalFunc usa esa copia local de $ var. El "local" en outsideFunc significa que lo global no ha cambiado. Si llama a innerFunc fuera de outsideFunc, entonces habrá un cambio en el $ var global, pero no en el $ var local del externalFunc. Si agrega "local" a innerFunc, entonces $ var de externalFunc no se cambiaría, en esencia, habría 3 de ellos:

  • $ global :: var
  • $ outsideFunc :: var
  • $ innerFunc :: var

usar el formato de espacio de nombres de Perl, más o menos.

Afbach
fuente
2

Puede usar una función para forzar el alcance local:

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

Ejemplo:

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"

Resultado:

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]

Fuente

Steven Penny
fuente
2

En function innerFunc()el var='new value'no se declaró como local , por lo tanto, está disponible en alcance visible (una vez que se ha llamado a la función).

Por el contrario, en function outerFunc()el local var='initial value'se declaró como local , por lo tanto, no está disponible en el ámbito global (incluso si se ha llamado a la función).

Como innerFunc()se llamó como hijo de outerFunc(), var está dentro del ámbito local de outerFunc().

man 1 bash puede ayudar a aclarar

local [opción] [nombre [= valor] ...]

Para cada argumento, se crea una variable local llamada nombre y se le asigna un valor. La opción puede ser cualquiera de las opciones aceptadas por declare. Cuando se usa local dentro de una función, hace que el nombre de la variable tenga un alcance visible restringido a esa función y sus elementos secundarios. ...

El comportamiento que se espera implícita en la descripción podría lograrse mediante la que se declara local var='new valueen function innerFunc().

Como han dicho otros, esto no es un error en el shell bash. Todo funciona como debería.

Joseph Tingiris
fuente
Su primera declaración contradice lo que el usuario está viendo. Imprimir el valor de varen el ámbito global, después de llamar innerFunca través outFunc, no se imprime new value.
Kusalananda