Advanced Bash-Scripting Guide:
Prev |  | Next

# Chapter 28. Indirect References

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

The actual notation is `\$$var`, usually preceded by an eval (and sometimes an echo). 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

Of what practical use is indirect referencing of variables? It gives Bash a little of the functionality of pointers in C, for instance, in table lookup. 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 sourcing 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 $?

| 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 version 2 of Bash (see Example 37-2 and Example A-22) makes indirect referencing more intuitive.

Prev | Home | Next
Arrays | Up | `/dev` and `/proc`