15 April 2015

sh-indirect-reference.png

Do you know that both bash and ksh support indirect variable reference (though in different ways)? Compared with "eval", this is clearer and conciser. Let me illustrate this using an example.

Suppose we have two variables var1 and var2. And, there is also a variable ref which refers to either var1 or var2. However, the value of ref is decided at run time.

var1="foo bar"
var2="hello world"
# ..., after some calculation, we got this:
ref="var2"

To get the intended value (i.e. "hello world" in this example), the "eval" way could be some like this:

value=$(eval "echo \$$ref")   # value: hello world

For bash, this code can be simplified using ${!ref}

value=${!ref}                 # value: hello world

In contrast, in ksh we define a "name reference" using nameref ref.

$ var1="foo bar"

$ var2="hello world"

$ nameref ref=var2

$ echo $ref
hello world

Indirect reference to BASH arrays

arr=(a b c d)
array_name=arr

ref_to_array=${array_name}[@]   # i.e. ref_to_array=arr[@]
for each in "${!ref_to_array}"; do  echo $each; done

ref_to_second_elem=${array_name}[1]
echo ${!ref_to_second_elem}

KSH Caveats

Although it provids some convenience, the name reference mechanism in KSH can be tricky. One have to be very clear about what is changing, the reference itself or the referred variable? Here are some examples illustrating some subtle details.

$ read ref          # change the value of var2
oops
$ echo $ref
oops
$ echo $var2
oops

$ ref=var1          # change the value of var2
$ echo $ref
var1
$ echo $var2
var1

$ unset -n ref      # unset the name reference
$ ref=var2          # assign value to ref, which is a normal var so far
$ echo $ref
var2
$ nameref ref       # making it a reference
$ echo $ref
hello world

$ unset -n ref      
$ var2="hello world"
$ nameref ref       # declare a name reference
$ ref=var2          # the first assignment changes reference itself
$ echo $ref
hello world
$ ref=var1          # the second assignment changes the value of referred variable
$ echo $ref
var1
$ echo $var2
var1

$ ksh --version
  version         sh (AT&T Research) 93u+ 2012-08-01

$ unset -n ref
$ read ref          # declare nameref AFTER read, works
var2
$ echo $ref
var2
$ nameref ref
$ echo $ref
hello world

$ unset -n ref      # declare nameref BEFORE read, oops
$ nameref ref
$ read ref
var2
$ echo $ref
Segmentation fault (core dumped)


blog comments powered by Disqus