Skip to content

Instantly share code, notes, and snippets.

@weakish
Created February 21, 2011 07:02
Show Gist options
  • Star 2 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save weakish/836753 to your computer and use it in GitHub Desktop.
Save weakish/836753 to your computer and use it in GitHub Desktop.
local and readonly variables in #shell

Local and readonly variables in shells

I've no idea how shells implement them. In this article, I will pretend they use a stack based implementation.

Ksh93 never pops out values. When dash encounteres 'local', it just reuse the outer value. Bash and pdksh/mksh unset values first.

Considering the following script: (no-readonly.sh)

x='global'

g() {
  local x
  x='g'
  readonly x
  echo 'x in g is' $x
}
f() {
  local x
  x='f'
  readonly x
  echo 'x in f is' $x
  g
  echo 'after run g, x in f' is $x
}

echo global x is $x
f
echo global x is still $x

dash, bash, and mksh work. But ksh fails. After running g, x is still 'g'.

Image the stacks:

scope|  assignments   |  ksh          dash          bash/pdksh/mksh
-----+----------------+----------------------------------------------
top  |  x='global'    |  |global      |global       |global
f    |  local x       |  |global      |global       ||global
f    |  x='f'         |  |f|global    |f|global     |f||global
g    |  local x       |  |f|global    |f|global     ||f||globas
g    |  x='g'         |  |g|f|global  |g|f|global   |g||f||global
f    |                |  |g|f|global  |f|global     |f||global
top  |                |  |g|f|global  |global       |global

Considering readonly, things goes tricky. ksh/dash seems just refuse to push values to readonly variables. bash refuses to push values to global variables, but not local variables. pdksh/mksh pushes normally.

dash's behaviour is not expected but reasonable. From the very moment you say local var, the var inherits values and attributes from the outer scope automatically. So you cannot assign new value to it. Though it's werid to inheriting values and attributes from the outer scope.

Bash is not consistent on top level and function level, which is intentional to provide special protection for global readonly variables.

Conclusion

pdksh/mksh is my best friend. Usually Bash won't bite me if I be careful about global variable names. For dash, I guess sed -e 's/readonly//' script.mksh > script.dash will work.

Test scripts

I've wrote some tiny test scripts, and put them in this repo.

Update

zsh works as expected, too.

x='global'
g() {
local x
readonly x=$1
echo $x
}
f() {
local x
readonly x='f'
g $x
}
f
readonly x='global' # Comment out this, otherwise only pdksh & mksh can pass.
f() {
local x
readonly x='f'
echo $x
}
g() {
local x
readonly x='g'
echo $x
}
f
g
# Change the above 'local' to 'typeset', and ksh93 failed. (ksh93 hasno local builtin.)
# dash, bash can pass the above test.
h() {
e() {
local x
readonly x='e'
echo $x
}
local x
readonly x
echo $x
e
echo $x
}
h
# bash can pass, while dash fails.
x='global'
#readonly x
g() {
local x
x=$1
readonly x
echo $x
}
f() {
local x
x='f'
readonly x
g $x
}
f
f() {
local x
x='f'
g() {
local x
echo $x
}
g
}
f
# bash, mksh, pdksh produces empty value.
# dash and ksh (with local->local) produces 'f'.
# In dash, local variable will inherit from the outter scope.
x='global'
g() {
local x
x='g'
echo 'x in g is' $x
}
f() {
local x
x='f'
echo 'x in f is' $x
g
echo 'after run g, x in f' is $x
}
echo global x is $x
f
echo global x is still $x
# dash, bash, mksh work. But ksh fails. After running g, x is still
# 'g'.
# `local readonly x='x'` set two local variables: readonly & x.
# I guess you mean `local x; readonly x='x'`.
f() {
local readonly x='x'
x='y'
echo $x
}
f
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment