Scope of Local Variables in Shell Functions
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:
-
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 syntaxfunction function_name { … }
then its local variable obey lexical/static scoping, so they are not visible in other functions called by this. -
the
zsh/private
autoloadable plugin inzsh
provides with aprivate
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]
Related videos on Youtube
maddouri
Updated on September 18, 2022Comments
-
maddouri over 1 year
After reading 24.2. Local Variables, I thought that declaring a variable
var
with the keywordlocal
meant thatvar
'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 thoughvar
is declaredlocal
toouterFunc
,innerFunc
is still able to read it and alter its value.#!/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 about 8 yearsIt is the expected behavior
-
Harold Fischer over 5 yearsAm 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 ininnerFunc
, so why doesn't it stick until the end of the script?
-
-
Kusalananda over 5 yearsYour first statement contradicts what the user is seeing. Printing the value of
var
in the global scope, after callinginnerFunc
throughoutFunc
, does not printnew value
. -
Harold Fischer over 5 yearsDo all variables in the shell have a dynamic scope or does this only apply to variables declared with
local
? -
Gilles 'SO- stop being evil' over 5 years@HaroldFischer All variables have dynamic scope. With a
typeset
ordeclare
orlocal
declaration, the scope is until the function returns. Without such a declaration, the scope is global. -
rosshjb almost 4 yearsIMHO,
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' 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 over 3 yearsDoes this also affect function calling builtins? Or does builtins have their own scope?
-
Franklin Yu over 3 yearsAlso note that the module is (now?) called
zsh/param/private
. -
Greg Minshall about 3 yearsiiuc, in this code,
x
is running in a separate process (because ofcommand
), so its logging message is coming from that process, not from the process in whichy
is running. -
user1730706 about 3 years@GregMinshall I dont really know, shell code is awful, so I dont use it anymore really