26

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.

  • It is the expected behavior – fpmurphy May 11 '16 at 17:09
  • 1
    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? – Harold Fischer Feb 5 at 3:42
20

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

There is an exception: in ATT ksh, 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 scoping, so they are not visible in other functions called by this. But bash, mksh and zsh only have dynamic scoping.

  • Do all variables in the shell have a dynamic scope or does this only apply to variables declared with local? – Harold Fischer Jan 26 at 0:56
  • @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. – Gilles Jan 26 at 14:46
5

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.

1

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

1

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.

  • 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. – Kusalananda Oct 17 '18 at 18:25

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.