shellshdash-shell

The scope of local variables in sh


I've got quite a lot of headaches trying to debug my recursive function. It turns out that Dash interprets local variables strangely. Consider the following snippet:

iteration=0;

MyFunction()
{
    local my_variable;

    iteration=$(($iteration + 1));

    if [ $iteration -lt 2 ]; then
        my_variable="before recursion";
        MyFunction
    else
        echo "The value of my_variable during recursion: '$my_variable'";
    fi
}

MyFunction

In Bash, the result is:

The value of my_variable during recursion: ''

But in Dash, it is:

The value of my_variable during recursion: 'before recursion'

Looks like Dash makes the local variables available across the same function name. What is the point of this and how can I avoid issues when I don't know when and which recursive iteration changed the value of a variable?


Solution

  • This is a consequence of your attempt to read the variable in the inner-most invocation without having set it in there explicitly. In that case, the variable is indeed local to the function, but it inherits its initial value from the outer context (where you have it set to "before recursion").

    The local marker on a variable thus only affects the value of the variable in the caller after the function invocation returned. If you set a local variable in a called function, its value will not affect the value of the same variable in the caller.

    To quote the dash man page:

    Variables may be declared to be local to a function by using a local command. This should appear as the first statement of a function, and the syntax is

     local [variable | -] ...
    

    Local is implemented as a builtin command.

    When a variable is made local, it inherits the initial value and exported and readonly flags from the variable with the same name in the surrounding scope, if there is one. Otherwise, the variable is initially unset. The shell uses dynamic scoping, so that if you make the variable x local to function f, which then calls function g, references to the variable x made inside g will refer to the variable x declared inside f, not to the global variable named x.

    The only special parameter that can be made local is “-”. Making “-” local any shell options that are changed via the set command inside the function to be restored to their original values when the function returns.

    To be sure about the value of a variable in a specific context, make sure to always set it explicitly in that context. Else, you rely on "fallback" behavior of the various shells which might be different across shells.