Advanced Bash-Scripting Guide: An in-depth exploration of the art of shell scripting
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

       1 #!/bin/bash
       2 # ind-ref.sh: Indirect variable referencing.
       3 # Accessing the contents of the contents of a variable.
       4
       5 # First, let's fool around a little.
       6
       7 var=23
       8
       9 echo "\$var   = $var"           # $var   = 23
      10 # So far, everything as expected. But ...
      11
      12 echo "\$\$var  = $$var"         # $$var  = 4570var
      13 #  Not useful ...
      14 #  \$\$ expanded to PID of the script
      15 #  -- refer to the entry on the $$ variable --
      16 #+ and "var" is echoed as plain text.
      17 #  (Thank you, Jakob Bohm, for pointing this out.)
      18
      19 echo "\\\$\$var = \$$var"       # \$$var = $23
      20 #  As expected. The first $ is escaped and pasted on to
      21 #+ the value of var ($var = 23 ).
      22 #  Meaningful, but still not useful.
      23
      24 # Now, let's start over and do it the right way.
      25
      26 # ============================================== #
      27
      28
      29 a=letter_of_alphabet   # Variable "a" holds the name of another variable.
      30 letter_of_alphabet=z
      31
      32 echo
      33
      34 # Direct reference.
      35 echo "a = $a"          # a = letter_of_alphabet
      36
      37 # Indirect reference.
      38   eval a=\$$a
      39 # ^^^        Forcing an eval(uation), and ...
      40 #        ^   Escaping the first $ ...
      41 # ------------------------------------------------------------------------
      42 # The 'eval' forces an update of $a, sets it to the updated value of \$$a.
      43 # So, we see why 'eval' so often shows up in indirect reference notation.
      44 # ------------------------------------------------------------------------
      45   echo "Now a = $a"    # Now a = z
      46
      47 echo
      48
      49
      50 # Now, let's try changing the second-order reference.
      51
      52 t=table_cell_3
      53 table_cell_3=24
      54 echo "\"table_cell_3\" = $table_cell_3"            # "table_cell_3" = 24
      55 echo -n "dereferenced \"t\" = "; eval echo \$$t    # dereferenced "t" = 24
      56 # In this simple case, the following also works (why?).
      57 #         eval t=\$$t; echo "\"t\" = $t"
      58
      59 echo
      60
      61 t=table_cell_3
      62 NEW_VAL=387
      63 table_cell_3=$NEW_VAL
      64 echo "Changing value of \"table_cell_3\" to $NEW_VAL."
      65 echo "\"table_cell_3\" now $table_cell_3"
      66 echo -n "dereferenced \"t\" now "; eval echo \$$t
      67 # "eval" takes the two arguments "echo" and "\$$t" (set equal to $table_cell_3)
      68
      69
      70 echo
      71
      72 # (Thanks, Stephane Chazelas, for clearing up the above behavior.)
      73
      74
      75 #   A more straightforward method is the ${!t} notation, discussed in the
      76 #+ "Bash, version 2" section.
      77 #   See also ex78.sh.
      78
      79 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.

       1 #!/bin/bash
       2
       3
       4 # ---------------------------------------------
       5 # This could be "sourced" from a separate file.
       6 isdnMyProviderRemoteNet=172.16.0.100
       7 isdnYourProviderRemoteNet=10.0.0.10
       8 isdnOnlineService="MyProvider"
       9 # ---------------------------------------------
      10
      11
      12 remoteNet=$(eval "echo \$$(echo isdn${isdnOnlineService}RemoteNet)")
      13 remoteNet=$(eval "echo \$$(echo isdnMyProviderRemoteNet)")
      14 remoteNet=$(eval "echo \$isdnMyProviderRemoteNet")
      15 remoteNet=$(eval "echo $isdnMyProviderRemoteNet")
      16
      17 echo "$remoteNet"    # 172.16.0.100
      18
      19 # ================================================================
      20
      21 #  And, it gets even better.
      22
      23 #  Consider the following snippet given a variable named getSparc,
      24 #+ but no such variable getIa64:
      25
      26 chkMirrorArchs () {
      27   arch="$1";
      28   if [ "$(eval "echo \${$(echo get$(echo -ne $arch |
      29        sed 's/^\(.\).*/\1/g' | tr 'a-z' 'A-Z'; echo $arch |
      30        sed 's/^.\(.*\)/\1/g')):-false}")" = true ]
      31   then
      32      return 0;
      33   else
      34      return 1;
      35   fi;
      36 }
      37
      38 getSparc="true"
      39 unset getIa64
      40 chkMirrorArchs sparc
      41 echo $?        # 0
      42                # True
      43
      44 chkMirrorArchs Ia64
      45 echo $?        # 1
      46                # False
      47
      48 # Notes:
      49 # -----
      50 # Even the to-be-substituted variable name part is built explicitly.
      51 # The parameters to the chkMirrorArchs calls are all lower case.
      52 # The variable name is composed of two parts: "get" and "Sparc" . . .

Example 28-2. Passing an indirect reference to awk

       1 #!/bin/bash
       2
       3 #  Another version of the "column totaler" script
       4 #+ that adds up a specified column (of numbers) in the target file.
       5 #  This one uses indirect references.
       6
       7 ARGS=2
       8 E_WRONGARGS=85
       9
      10 if [ $# -ne "$ARGS" ] # Check for proper number of command-line args.
      11 then
      12    echo "Usage: `basename $0` filename column-number"
      13    exit $E_WRONGARGS
      14 fi
      15
      16 filename=$1         # Name of file to operate on.
      17 column_number=$2    # Which column to total up.
      18
      19 #===== Same as original script, up to this point =====#
      20
      21
      22 # A multi-line awk script is invoked by
      23 #   awk "
      24 #   ...
      25 #   ...
      26 #   ...
      27 #   "
      28
      29
      30 # Begin awk script.
      31 # -------------------------------------------------
      32 awk "
      33
      34 { total += \$${column_number} # Indirect reference
      35 }
      36 END {
      37      print total
      38      }
      39
      40      " "$filename"
      41 # Note that awk doesn't need an eval preceding \$$.
      42 # -------------------------------------------------
      43 # End awk script.
      44
      45 #  Indirect variable reference avoids the hassles
      46 #+ of referencing a shell variable within the embedded awk script.
      47 #  Thanks, Stephane Chazelas.
      48
      49
      50 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`