There was a thread caught my attentions, which is a topic I wanted to write a long time ago, but never started writing about it. To cut it short, although the thread isn’t long, the OP wants to do something like pointer in C, roughly speaking.

A quick example:


x=a
echo x=$x
echo a=$a
echo ----
let $x=1
echo $x=${!x}

x=a
a=
----
a=1

At this point, you should have understood what it is about. In the example above, the x is the pointer, which stores the name of variable, which is the variable will be updated. So x does not store an address but an indirect reference, but you get the idea. The code uses let to do arithmetic operation, you can use eval if you like.

At first glance, you may find little weird, but think let process a string, therefore you expand variables first , i.e.:


let $x=1 => let a=1

Hench, there could be some side-effects:


x='b=3 a'
let $x=1
echo $a $b

1 3

It’s sort of like limited version of SQL injection, but I doubt it will really do any harm. But, not so fast:


x='b="$(echo 3)" a'
eval $x=1
echo $a $b

1 3

You can run any command with user’s permission. However, I don’t think there is much chance that someone can feed stuff into x.

Now, back to a more practical example. The following code is for counting how many times a year is listed in data.


data='2009 1998 2012 2003 2005 1995 2012 2004 2011 2003 2005'

for year in $data; do
let y$year++
eval "n$year='Year $year'"
# or
# eval n$year='Year\ $year'
done

echo -e "Variables prefixed by letter y:\n ${!y*}"
echo -e "Variables prefixed by letter n:\n ${!n*}"

for key in ${!y*}; do
[[ $key == 'year' ]] && continue
name=${key/y/n}
echo ${!name} = ${!key} \($key\)
# or
# eval echo \$$name = \$$key \\\($key\\\)
done

Variables prefixed by letter y:
y1995 y1998 y2003 y2004 y2005 y2009 y2011 y2012 year
Variables prefixed by letter n:
n1995 n1998 n2003 n2004 n2005 n2009 n2011 n2012
Year 1995 = 1 (y1995)
Year 1998 = 1 (y1998)
Year 2003 = 2 (y2003)
Year 2004 = 1 (y2004)
Year 2005 = 2 (y2005)
Year 2009 = 1 (y2009)
Year 2011 = 1 (y2011)
Year 2012 = 2 (y2012)

As you can see, it uses let for number and eval for string. Because let can not process string assignment, however it won’t fail, but the content in the variable is not what you expect. So use eval for non-arithmetic operations.

Once you have data processed, you need a way to get them. More precisely, the variables names, which can be retrieve by a special expansion ${!prefix*} or ${!prefix@}. It expands into a list of variable names. If you do not understand the difference of * and @, you can read this post. For variable names, using * is safe.

In the last part, you can see ${!name}, which is called Indirect expansion. The content in $name is the variable name, that is what we are after. I don’t know if the syntax is Bash only or not, you should be able to use eval with \$$name for POSIX shell.

This example intentionally is made for demonstration of when you do not have control of year ranges. You do not know the years of beginning and end before hand. Certainly, you can simply do let year[2012]++ and ${!year[*]} for indexes, but arrays is not the topic here.

Bash is a powerful programming language. Most of time, it’s easy to work with. It’s not hard or messy, just I often forget how many features it has and I code in very lengthy and ugly way.