12

I can do this, but it requires making a string of the variable then dereferencing it. Is there any way to shorten it to a simpler statement?

#!/bin/bash

FRUITS="BANANA APPLE ORANGE"

BANANA_COLOUR="Yellow"
APPLE_COLOUR="Green or Red"
ORANGE_COLOUR="Blue"

for fruit in $( echo $FRUITS ); do
    fruit_colour="${fruit}_COLOUR"
    echo $fruit is ${!fruit_colour}
done

I have tried lots of things like ${!"${fruit}_COLOUR"} or \$${fruit}_COLOUR and many other variants, but the only way that has worked is by using a string.

  • The ! syntax is a flag on variable substitution that basically says “substitute twice”. It doesn't change the requirement that what's inside the ${…} must be a variable name. So no, you can't avoid using a variable that contains the name, unless you use a different method (eval). – Gilles 'SO- stop being evil' Feb 21 '12 at 0:31
  • You can also use associative arrays to do this much more cleanly. However its not a direct answer to your question, so just adding it as a comment. – Patrick Feb 21 '12 at 1:29
10

First off, you don't need to use $(echo $FRUITS) in the for statement. Using just $FRUITS is enough. Then you can do away with one of the lines inside the loop, by using eval.

The eval simply tells bash to make a second evaluation of the following statement (i.e. one more than its normal evaluation). The \$ survives the first evaluation as $, and the next evaluation then treats this $ as the start of a variable name, which resolves to "Yellow", etc.

This way you don't need to have a separate step which makes an interim string (which is what I believe was the main intent of your question).

for fruit in $FRUITS ;do
    eval echo $fruit is \$${fruit}_COLOUR
done

For an alternative method, as mentioned by Patrick in a comment (above), you can instead use an associative array, in which an element's index does not need to be an integer. You can use a string, such as the name of a type of fruit. Here is an example, using bash's associative array:

# This declares an associative array (It unsets it if it already exists)
declare -A colour
colour['BANANA']="Yellow"
colour["APPLE"]="Green or Red"
colour[MARTIAN ORANGE]="Blue"

for fruit in BANANA APPLE "MARTIAN ORANGE" ;do 
    echo "$fruit" is "${colour[$fruit]}"
done
6

You can use the bash-builtin eval to do that:

#!/bin/bash
FRUITS="BANANA APPLE ORANGE"
BANANA_COLOUR="Yellow"
APPLE_COLOUR="Green or Red"
ORANGE_COLOUR="Blue"

for fruit in $( echo $FRUITS );
do
    fruit_colour=${fruit}_COLOUR
    eval echo $fruit is \$${fruit_colour}
done

Note the backslashed-dollar sign. Basically, the "eval" line causes bash to substitue for $fruit and ${fruit_color}, then using eval to do a second round of substition before calling echo.

0

You don't need an eval statement. eval is a hint in most languages that you're doing something wrong.

foo_var=10
bar_var=12

# Build a string with your var name of choice
chosen_prefix='foo'
var_name="${chosen_prefix}_var"

# Use bang to deference it
chossen_value=${!var_name}
echo "Chossen value: ${chossen_value}"
  • I'd argue it's safer to use eval because at least people know it's dangerous, ${!var} is about as dangerous in bash (also a command injection vulnerability if $chosen_prefix is under an attacker's control) and fewer people are aware of it. Try var_name='a[$(uname>&2)0]' bash -c 'v=${!var_name}'. Best approach here is the associative array. – Stéphane Chazelas Aug 13 '18 at 12:31

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.