Shell Locality
When a shell function declares a variable to be local
and then unsets it,
does the name return to being global in scope or is it still “known” to be
local, even though unset (via some kind of tombstone mechanism, perhaps)?
Let’s test it. Spoiler: the results vary.
Note that POSIX does not provide local
, this is a shell extension.
The Test
Here is our test as a pasteable one-liner:
i=0; s() { echo "$i: x: $x"; i=$((i+1));}; x=outside; s; foo() { s; local x; s;x=inside;s;unset x;s;unset x; s; }; foo; s
Breaking that down:
i=0 # loop-counter for our "show" function so we can see the steps
s() { # a simple show function, using POSIX $((...)) for arithmetic
echo "$i: x: $x"
i=$((i+1))
}
x=outside # our test variable is x and we give it a maskable value
s # 0 -- prior state
foo() {
s # 1 -- inside function, before local
local x # from this point on, if `local` exists, x is non-global
s # 2 -- does the act of making the variable local clear its value?
x=inside # our "inside" value
s # 3 -- confirming value is set and variables work
unset x # reset x ... but what is left behind?
s # 4 -- should see an empty string
unset x # unknown: does this unset the global?
s # 5 -- final display inside the function
}
foo # we need to call the function, since only zsh has anonymous functions
s # 6 -- outside the function, show the global after double unset
The Results
Zsh
0: x: outside
1: x: outside
2: x:
3: x: inside
4: x:
5: x:
6: x: outside
- The
local
keyword resets the value of the variable (counter 2). - The second
unset
did not unset the global (counter 6).
Bash (5)
0: x: outside
1: x: outside
2: x:
3: x: inside
4: x:
5: x:
6: x: outside
[edited to add Bash options:]
Bash behaves the same as zsh, by default.
There is an option localvar_inherit
to change this:
shopt -s localvar_inherit
foo
0: x: outside
1: x: outside
2: x: outside
3: x: inside
4: x:
5: x:
6: x: outside
There is also an option localvar_unset
which impacts a more complicated form
than we’re addressing here.
Dash
0: x: outside
1: x: outside
2: x: outside
3: x: inside
4: x:
5: x:
6: x: outside
- The
local
keyword in dash does NOT reset the visible value, but does make future modifications only visible within local scope; this matches Bash withlocalvar_inherit
. - The second
unset
did not unset the global.
Ksh
This test was of the shell from apk add -U loksh
inside an Alpine 3.12
Docker container, which installed version 6.7.1-r0
of loksh
, described as
“A Linux port of OpenBSD’s ksh”.
0: x: outside
1: x: outside
2: x:
3: x: inside
4: x: outside
5: x:
6: x:
- The
local
keyword in ksh is immediately resetting the visible value. - The second
unset
inside the function did unset the global variable!
FreeBSD sh
0: x: outside
1: x: outside
2: x:
3: x: inside
4: x:
5: x:
6: x: outside
This is the same as zsh and bash’s default.
Conclusion
Writing portable shell is hard. Duh.