Escopo de variáveis ​​locais em funções de shell

28

Depois de ler 24.2. Variáveis ​​locais , pensei que declarar uma variável varcom a palavra-chave localsignificava que varo valor de s só era acessível dentro do bloco de código delimitado pelas chaves de uma função.

No entanto, depois de executar o exemplo a seguir, descobri que vartambém pode ser acessado, lido e escrito das funções invocadas por esse bloco de código - ou seja, mesmo que varseja declarada locala outerFunc, innerFuncainda é capaz de lê-lo e alterar seu 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}]"

Saída:

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: Isso é um bug no meu shell (bash 4.3.42, Ubuntu 16.04, 64bit) ou é o comportamento esperado?

EDIT: resolvido. Conforme observado por @MarkPlotnick, esse é realmente o comportamento esperado.

Maddouri
fonte
É o comportamento esperado
fpmurphy
2
Eu sou o único que acha estranho que, na última linha de saída, o valor de varesteja vazio? varestá definido globalmente innerFunc, então por que não permanece até o final do script?
Harold Fischer

Respostas:

22

Variáveis ​​de shell têm um escopo dinâmico . Se uma variável for declarada como local para uma função, esse escopo permanecerá até que a função retorne.

Existem duas exceções:

  1. no ksh93, se uma função é definida com a function_name () { … }sintaxe padrão , suas variáveis ​​locais obedecem ao escopo dinâmico. Mas se uma função é definida com a sintaxe ksh function function_name { … }, sua variável local obedece ao escopo lexical / estático, portanto, elas não são visíveis em outras funções chamadas por isso.

  2. o zsh/privateplug-in de carregamento automático zshfornece uma privatepalavra - chave / builtin que pode ser usada para declarar uma variável com escopo estático.

ash, bash, pdksh e derivados, o bosh possui escopo dinâmico.

Gilles 'SO- parar de ser mau'
fonte
Todas as variáveis ​​no shell têm um escopo dinâmico ou isso se aplica apenas a variáveis ​​declaradas com local?
Harold Fischer
@HaroldFischer Todas as variáveis ​​têm escopo dinâmico. Com uma typesetou declareou localdeclaração, o escopo é até que a função retorna. Sem essa declaração, o escopo é global.
Gilles 'SO- stop be evil'
6

Não é um bug, a chamada dentro do contexto do outerFunc usa essa cópia local de $ var. O "local" em outerFunc significa que o global não é alterado. Se você chamar innerFunc fora de outerFunc, haverá uma alteração no $ var global, mas não no $ var local do outerFunc. Se você adicionou "local" ao innerFunc, o $ var do outerFunc não seria alterado - em essência, haveria três deles:

  • $ global :: var
  • $ outerFunc :: var
  • $ innerFunc :: var

para usar o formato de espaço para nome do Perl, mais ou menos.

afbach
fonte
2

Você pode usar uma função para forçar o escopo local:

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

Exemplo:

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]

Fonte

Steven Penny
fonte
2

function innerFunc()No var='new value'não foi declarado como locais , portanto, ele está disponível em âmbito visível (uma vez que a função foi chamado).

Por outro lado, function outerFunc()na local var='initial value'foi declarado como locais , portanto, ele não está disponível no escopo global (mesmo que a função foi chamado).

Como innerFunc()foi chamado como filho de outerFunc(), var está dentro do escopo local de outerFunc().

man 1 bash pode ajudar a esclarecer

local [opção] [nome [= valor] ...]

Para cada argumento, uma variável local chamada name é criada e é atribuído um valor. A opção pode ser qualquer uma das opções aceitas por declarar. Quando local é usado em uma função, faz com que o nome da variável tenha um escopo visível restrito a essa função e seus filhos. ...

O comportamento implícito que é esperado na descrição poderia ser alcançado por declarar local var='new valueem function innerFunc().

Como outros já declararam, isso não é um bug no shell bash. Tudo está funcionando como deveria.

Joseph Tingiris
fonte
Sua primeira declaração contradiz o que o usuário está vendo. Imprimir o valor de varno escopo global, depois de chamar innerFuncatravés outFunc, não imprime new value.
Kusalananda