Dynamic Variables in BASH
Assigning variables is easy if you know how many you have and what they are called. The value of a variable can be, well, variable. But the names and number of variables need to be known ahead of time. Or do they? I am not a big fan of using “expr” and “eval” in shell scripts, but sometimes there’s no other option.
Let’s say you need to assign variables var1 through var5. For example, here we set var1 through var5 to a random number between 1 and 100:
var1=$(echo "`expr ${RANDOM} % 100`+1"|bc -l) var2=$(echo "`expr ${RANDOM} % 100`+1"|bc -l) var3=$(echo "`expr ${RANDOM} % 100`+1"|bc -l) var4=$(echo "`expr ${RANDOM} % 100`+1"|bc -l) var5=$(echo "`expr ${RANDOM} % 100`+1"|bc -l) echo "${var1}, ${var2}, ${var3}, ${var4}, ${var5}" > 36, 16, 99, 3, 74
Now imagine you need to assign var1 through var100, or, worse yet, var1 through varN, where N is not know ahead of time and is determined dynamically during the script’s execution. This can be an annoying little problem.
In the following example, the N is determined as a random number between 1 and 100. In this case it happened to be 25. So we need to set var1 through varN to a random number between 1 and 100. And then we need to display those values.
N=$(echo "`expr ${RANDOM} % 100`+1"|bc -l) for i in `seq 1 ${N}` do eval "$(echo var${i})"=$(echo "`expr ${RANDOM} % 100`+1"|bc -l) done for i in `seq 1 ${N}` do eval echo -n $(echo $`eval echo "var${i},"`) done > 6,56,31,11,89,49,26,9,14,77,8,90,55,87,39,63,99,28,12,1,43,80,44,87,27,
So we just created a random number of variables assigned random values within the established constraints. If you don’t want to deal with the hard-to-predict “eval”, a far more elegant solution is to use a shell array. Here’s an example that produces a similar result:
N=$(echo "`expr ${RANDOM} % 100`+1"|bc -l) typeset -A array for i in `seq 1 ${N}` do array[var${i}]=$(echo "`expr ${RANDOM} % 100`+1"|bc -l) done echo ${array[*]} > 37 77 21 39 73 5 11 47 2 6 91 67 69 70 34 30 61 49 67 46 82 69 68 18
Thus, the unsightly construct
i=1 ; eval echo $(echo $`eval echo "var${i}"`)
can be replaced with a less cumbersome
i=1 ; echo ${array[var${i}]}
1
# In this case $i contains characters that cannot be used in a Bash variable name $ i="12-23=32#" $ echo var$i var12-23=32# # The "var12-23=32#" cannot be a variable name. When assigning variable value, we will need to replace all non-alphanumeric characters with underscores. eval "$(echo var${i//[^[:alnum:]]/_})"=$(echo "`expr ${RANDOM} % 100`+1"|bc -l) eval echo $(echo $`eval echo "var${i//[^[:alnum:]]/_}"`) # The variable name will become "var12_23_32_", so the last line above should show the same value as: echo $var12_23_32_
The inline replacement also works when using the array:
N=$(echo "`expr ${RANDOM} % 100`+1"|bc -l) typeset -A array for i in `seq 1 ${N}` do array[var${i//[^[:alnum:]]/_}]=$(echo "`expr ${RANDOM} % 100`+1"|bc -l) done echo ${array[*]} > 90 24 43 64 52 50 82 75 69 84 91 38 61 28 41 84 56 5 26