Scope of Local Variables in Shell Functions

68,244

Solution 1

Shell variables have a dynamic scope. If a variable is declared as local to a function, that scope remains until the function returns, including during calls to other functions.

There are two exceptions:

  1. in ksh93, if a function is defined with the standard function_name () { … } syntax, then its local variables obey dynamic scoping. But if a function is defined with the ksh syntax function function_name { … } then its local variable obey lexical/static scoping, so they are not visible in other functions called by this.

  2. the zsh/private autoloadable plugin in zsh provides with a private keyword/builtin which can be used to declare a variable with static scope.

ash, bash, pdksh and derivatives, bosh only have dynamic scoping.

Solution 2

It isn't a bug, the call inside the context of the outerFunc uses that local copy of $var. The "local" in outerFunc means the global isn't changed. If you call innerFunc outside of outerFunc, then there will be a change to the global $var, but not the outerFunc's local $var. If you added "local" to innerFunc, then outerFunc's $var wouldn't be changed - in essence, there'd be 3 of them:

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

to use Perl's namespace format, sort of.

Solution 3

In function innerFunc() the var='new value' wasn't declared as local, therefore it's available in visible scope (once the function has been called).

Conversely, in function outerFunc() the local var='initial value' was declared as local, therefore it's not available in the global scope (even if the function has been called).

Because innerFunc() was called as a child of outerFunc(), var is within the the local scope of outerFunc().

man 1 bash may help clarify

local [option] [name[=value] ...]

For each argument, a local variable named name is created, and assigned value. The option can be any of the options accepted by declare. When local is used within a function, it causes the variable name to have a visible scope restricted to that function and its children. ...

The implied behavior that's expected in the description could be achieved by declaring local var='new value in function innerFunc().

As others have stated, this is not a bug in the bash shell. Everything's functioning as it should.

Solution 4

You can use a function to force local scope:

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

Example:

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"

Result:

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

Share:
68,244

Related videos on Youtube

maddouri
Author by

maddouri

Updated on September 18, 2022

Comments

  • maddouri
    maddouri over 1 year

    After reading 24.2. Local Variables, I thought that declaring a variable var with the keyword local meant that var's value was only accessible within the block of code delimited by the curly braces of a function.

    However, after running the following example, I found out that var can also be accessed, read and written from the functions invoked by that block of code -- i.e. even though var is declared local to outerFunc, innerFunc is still able to read it and alter its value.

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

    Output:

    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: Is that a bug in my shell (bash 4.3.42, Ubuntu 16.04, 64bit) or is it the expected behavior ?

    EDIT: Solved. As noted by @MarkPlotnick, this is indeed the expected behavior.

    • fpmurphy
      fpmurphy about 8 years
      It is the expected behavior
    • Harold Fischer
      Harold Fischer over 5 years
      Am I the only one who thinks it's weird that on the last line of output the value of var is empty? var is set globally in innerFunc, so why doesn't it stick until the end of the script?
  • Kusalananda
    Kusalananda over 5 years
    Your first statement contradicts what the user is seeing. Printing the value of var in the global scope, after calling innerFunc through outFunc, does not print new value.
  • Harold Fischer
    Harold Fischer over 5 years
    Do all variables in the shell have a dynamic scope or does this only apply to variables declared with local?
  • Gilles 'SO- stop being evil'
    Gilles 'SO- stop being evil' over 5 years
    @HaroldFischer All variables have dynamic scope. With a typeset or declare or local declaration, the scope is until the function returns. Without such a declaration, the scope is global.
  • rosshjb
    rosshjb almost 4 years
    IMHO, If a variable is declared as local to a function, that scope remains until the function returns. is not enough to explain what is dynamic scope verus lexical scope. The description alone is also applied to lexical scope.
  • Gilles 'SO- stop being evil'
    Gilles 'SO- stop being evil' almost 4 years
    @jinbeomhong No, with lexical scope, a variable from a function is not visible while this function calls another function. I've added a sentence to state this explicitly.
  • Franklin Yu
    Franklin Yu over 3 years
    Does this also affect function calling builtins? Or does builtins have their own scope?
  • Franklin Yu
    Franklin Yu over 3 years
    Also note that the module is (now?) called zsh/param/private.
  • Greg Minshall
    Greg Minshall about 3 years
    iiuc, in this code, x is running in a separate process (because of command), so its logging message is coming from that process, not from the process in which y is running.
  • user1730706
    user1730706 about 3 years
    @GregMinshall I dont really know, shell code is awful, so I dont use it anymore really