Indirect References

Bash Unix Shell Scripting:

Chapter 28. Indirect References

We have seen that , $var, fetches its value. But, what about the value of a value? What about $$var?

The actual notation is \$$var, usually preceded by an (and sometimes an ). This is called an indirect reference.

Example 28-1. Indirect Variable References

#!/bin/bash
# ind-ref.sh: Indirect variable referencing.
# Accessing the contents of the contents of a variable.
# First, let's fool around a little.
var=23
echo "\$var   = $var"           # $var   = 23
# So far, everything as expected. But ...
echo "\$\$var  = $$var"         # $$var  = 4570var
#  Not useful ...
#  \$\$ expanded to PID of the script
#  -- refer to the entry on the $$ variable --
#+ and "var" is echoed as plain text.
#  (Thank you, Jakob Bohm, for pointing this out.)
echo "\\\$\$var = \$$var"       # \$$var = $23
#  As expected. The first $ is escaped and pasted on to
#+ the value of var ($var = 23 ).
#  Meaningful, but still not useful. 
# Now, let's start over and do it the right way.
# ============================================== #
a=letter_of_alphabet   # Variable "a" holds the name of another variable.
letter_of_alphabet=z
echo
# Direct reference.
echo "a = $a"          # a = letter_of_alphabet
# Indirect reference.
  eval a=\$$a
# ^^^        Forcing an eval(uation), and ...
#        ^   Escaping the first $ ...
# ------------------------------------------------------------------------
# The 'eval' forces an update of $a, sets it to the updated value of \$$a.
# So, we see why 'eval' so often shows up in indirect reference notation.
# ------------------------------------------------------------------------
  echo "Now a = $a"    # Now a = z
echo
# Now, let's try changing the second-order reference.
t=table_cell_3
table_cell_3=24
echo "\"table_cell_3\" = $table_cell_3"            # "table_cell_3" = 24
echo -n "dereferenced \"t\" = "; eval echo \$$t    # dereferenced "t" = 24
# In this simple case, the following also works (why?).
#         eval t=\$$t; echo "\"t\" = $t"
echo
t=table_cell_3
NEW_VAL=387
table_cell_3=$NEW_VAL
echo "Changing value of \"table_cell_3\" to $NEW_VAL."
echo "\"table_cell_3\" now $table_cell_3"
echo -n "dereferenced \"t\" now "; eval echo \$$t
# "eval" takes the two arguments "echo" and "\$$t" (set equal to $table_cell_3)
echo
# (Thanks, Stephane Chazelas, for clearing up the above behavior.)
#   A more straightforward method is the ${!t} notation, discussed in the
#+ "Bash, version 2" section.
#   See also ex78.sh.
exit 0

Indirect referencing in Bash is a multi-step process. First, take the name of a variable: varname. Then, reference it: $varname. Then, reference the reference: $$varname. Then, escape the first $: \$$varname. Finally, force a reevaluation of the expression and assign it: eval newvar=\$$varname.

Of what practical use is indirect referencing of variables? It gives Bash a little of the functionality of in C, for instance, in . And, it also has some other very interesting applications. . . .

Nils Radtke shows how to build "dynamic" variable names and evaluate their contents. This can be useful when configuration files.

#!/bin/bash
# ---------------------------------------------
# This could be "sourced" from a separate file.
isdnMyProviderRemoteNet=172.16.0.100
isdnYourProviderRemoteNet=10.0.0.10
isdnOnlineService="MyProvider"
# ---------------------------------------------
      
remoteNet=$(eval "echo \$$(echo isdn${isdnOnlineService}RemoteNet)")
remoteNet=$(eval "echo \$$(echo isdnMyProviderRemoteNet)")
remoteNet=$(eval "echo \$isdnMyProviderRemoteNet")
remoteNet=$(eval "echo $isdnMyProviderRemoteNet")
echo "$remoteNet"    # 172.16.0.100
# ================================================================
#  And, it gets even better.
#  Consider the following snippet given a variable named getSparc,
#+ but no such variable getIa64:
chkMirrorArchs () { 
  arch="$1";
  if [ "$(eval "echo \${$(echo get$(echo -ne $arch |
       sed 's/^\(.\).*/\1/g' | tr 'a-z' 'A-Z'; echo $arch |
       sed 's/^.\(.*\)/\1/g')):-false}")" = true ]
  then
     return 0;
  else
     return 1;
  fi;
}
getSparc="true"
unset getIa64
chkMirrorArchs sparc
echo $?        # 0
               # True
chkMirrorArchs Ia64
echo $?        # 1
               # False
# Notes:
# -----
# Even the to-be-substituted variable name part is built explicitly.
# The parameters to the chkMirrorArchs calls are all lower case.
# The variable name is composed of two parts: "get" and "Sparc" . . .

Example 28-2. Passing an indirect reference to awk

#!/bin/bash
#  Another version of the "column totaler" script
#+ that adds up a specified column (of numbers) in the target file.
#  This one uses indirect references.
ARGS=2
E_WRONGARGS=85
if [ $# -ne "$ARGS" ] # Check for proper number of command-line args.
then
   echo "Usage: `basename $0` filename column-number"
   exit $E_WRONGARGS
fi
filename=$1         # Name of file to operate on.
column_number=$2    # Which column to total up.
#===== Same as original script, up to this point =====#
# A multi-line awk script is invoked by
#   awk "
#   ...
#   ...
#   ...
#   "
# Begin awk script.
# -------------------------------------------------
awk "
{ total += \$${column_number} # Indirect reference
}
END {
     print total
     }
     " "$filename"
# Note that awk doesn't need an eval preceding \$$.
# -------------------------------------------------
# End awk script.
#  Indirect variable reference avoids the hassles
#+ of referencing a shell variable within the embedded awk script.
#  Thanks, Stephane Chazelas.
exit $?
Caution

This method of indirect referencing is a bit tricky. If the second order variable changes its value, then the first order variable must be properly dereferenced (as in the above example). Fortunately, the ${!variable} notation introduced with of Bash (see and ) makes indirect referencing more intuitive.

Bash does not support pointer arithmetic, and this severely limits the usefulness of indirect referencing. In fact, indirect referencing in a scripting language is, at best, something of an afterthought.