226

How can I retrieve a bash variable value if I have the variable name as string?

var1="this is the real value"
a="var1"
Do something to get value of var1 just using variable a.

Context:

I have some AMI's (Amazon Machine Image) and I want to fire up a few instances of each AMI. As soon as they finish booting, I want to setup each instance according to its AMI type. I don't want to bake lots of scripts or secret keys inside any AMI so I prepared a generalized startup script and I put it on S3 with a publicly accessible link. In rc.local I put small piece of code which fetches the startup script and executes it. This is all I have in the AMIs. Then each AMI accesses a common configuration script which is applicable to all AMIs and special setup scripts for each. These scripts are private and require a signed URL to access them.

So now, when I fire an instance of an AMI (my_private_ami_1), I pass a signed URL for one more file presented on S3 which contains signed URL for all private scripts in terms of key/value pair.

config_url="http://s3.amazo.../config?signature"
my_private_ami_1="http://s3.amazo.../ami_1?signature"
...
When the startup script runs, it downloads the above file and source's it. Then it checks for its AMI type and picks the correct setup script for itself.

ami\_type=GET AMI TYPE #ex: sets ami\_type to my\_private\_ami\_1
setup\_url=GET THE SETUP FILE URL BASED ON AMI\_TYPE # this is where this problem arises

So now I can have a generic code which can fire instances irrespective of their AMI types and instances can take care of themselves.

13 Answers 13

421

You can use ${!a}:

var1="this is the real value"
a="var1"
echo "${!a}" # outputs 'this is the real value'

This is an example of indirect parameter expansion:

The basic form of parameter expansion is ${parameter}. The value of parameter is substituted.

If the first character of parameter is an exclamation point (!), it introduces a level of variable indirection. Bash uses the value of the variable formed from the rest of parameter as the name of the variable; this variable is then expanded and that value is used in the rest of the substitution, rather than the value of parameter itself.

Sign up to request clarification or add additional context in comments.

13 Comments

This works for me in OSX bash, but not debian? On debian I get Bad substitution error.
@23inhouse Are you running your script using /bin/sh? If so, try using /bin/bash instead. From Debian Squeeze onwards, /bin/sh was changed to be a symlink for dash instead of bash. dash doesn't support this particular syntax and will output a Bad substitution error.
Is there a way to make this work for a variable name that belongs to an array?
@DoronShai This is an example of indirect parameter expansion, documented in the Bash Reference Manual at gnu.org/software/bash/manual/html_node/…
Wow! I almost thought this is not possible. How do you guys know this stuff?
|
46
X=foo
Y=X
eval "Z=\$$Y"

sets Z to foo.

1 Comment

under Linux-Bash: number=1 eval "Z=zuzu_\$number" echo "$Z" the Result is displaying: zuzu_1
26

For my fellow zsh users, the way to accomplish the same thing as the accepted answer is to use:

echo ${(P)a} # outputs 'this is the real value'

It is appropriately called Parameter name replacement. Also works with variables stored in arrays.

This forces the value of the parameter name to be interpreted as a further parameter name, whose value will be used where appropriate. Note that flags set with one of the typeset family of commands (in particular case transformations) are not applied to the value of name used in this fashion.

If used with a nested parameter or command substitution, the result of that will be taken as a parameter name in the same way. For example, if you have ‘foo=bar’ and ‘bar=baz’, the strings ${(P)foo}, ${(P)${foo}}, and ${(P)$(echo bar)} will be expanded to ‘baz’.

Likewise, if the reference is itself nested, the expression with the flag is treated as if it were directly replaced by the parameter name. It is an error if this nested substitution produces an array with more than one word. For example, if ‘name=assoc’ where the parameter assoc is an associative array, then ‘${${(P)name}[elt]}’ refers to the element of the associative subscripted ‘elt’.

Comments

17

In bash 4.3+, you can use declare -n:

#!/usr/bin/env bash

var="this is the real value"
var_name="var"

declare -n var_ref=$var_name
echo "${var_ref}"

Comments

15

Modified my search keywords and Got it :).

eval a=\$$a
Thanks for your time.

2 Comments

Take care using eval since this may allow accidential excution of code through values in ${Y}. See my addition in the answer of the user "anon".
This is the only working answer. Ironic that it is your own!
10

Had the same issue with arrays, here is how to do it if you're manipulating arrays too :

array_name="ARRAY_NAME"
ARRAY_NAME=("Val0" "Val1" "Val2")

# ARRAY is the value of array name with the string "[@]" concatenated
ARRAY="$array_name[@]"
echo "ARRAY=${ARRAY}"

# ARRAY is reset to the values obtained from indirect lookup
ARRAY=("${!ARRAY}")
echo "ARRAY=${ARRAY[@]}"

# a single value can be referenced by index
echo "ARRAY[1]=${ARRAY[1]}"

#the full ARRAY can be iterated over
for val in ${ARRAY[@]}; do
  echo "  val=${val}"
done

This will output :

ARRAY=ARRAY_NAME[@]
ARRAY=Val0 Val1 Val2
ARRAY[1]=Val1
  val=Val0
  val=Val1
  val=Val2

1 Comment

+1 for this is the only correct answer for array, to make it clearer, the string must have [@] suffix to obtain the full array.
2

In bash 4.3, the '-v' test for set variables was introduced. At the same time, 'nameref' declaration was added. These two features together with the indirection operator (!) enable a simplified version of the previous example:

get_value()
{
  declare -n var_name=$1
  if [[ -v var_name ]]
  then
    echo "${var_name}"
  else
    echo "variable with name <${!var_name}> is not set"
  fi
}

test=123
get_value test
123

test="\$(echo \"something nasty\")"
get_value test
$(echo "something nasty")

unset test
get_value test
variable with name <test> is not set

As this approach eliminates the need for 'eval', it is safer. This code checked under bash 5.0.3(1).

Comments

2

Extending the answer of @MrZ:

Using declare -n you can generate a reference to any variable whose name is generated dynamically. For example:

$ var_one='This is variable 1'
$ ind=one
$ declare -n var_ref=var_$ind
$ echo $var_ref
This is variable 1
$ ind=two
$ echo $var_ref
This is variable 1
$ declare -n var_ref=var_$ind
$ echo $var_ref

$ var_two="And this is the second variable"
$ echo $var_ref
And this is the second variable

1 Comment

This should be the accepted answer. This resolves the initial problem safely and using only bash functionality as requested, while also allowing assignments.
1

modern shells already support arrays( and even associative arrays). So please do use them, and use less of eval.

var1="this is the real value"
array=("$var1")
# or array[0]="$var1"

then when you want to call it , echo ${array[0]}

1 Comment

would this work if i get the string from commandline argument (e.g. as $1)?
0

Based on the answer: https://unix.stackexchange.com/a/111627

###############################################################################
# Summary: Returns the value of a variable given it's name as a string.
# Required Positional Argument: 
#   variable_name - The name of the variable to return the value of
# Returns: The value if variable exists; otherwise, empty string ("").
###############################################################################
get_value_of()
{
    variable_name=$1
    variable_value=""
    if set | grep -q "^$variable_name="; then
        eval variable_value="\$$variable_name"
    fi
    echo "$variable_value"
}

test=123
get_value_of test
# 123
test="\$(echo \"something nasty\")"
get_value_of test
# $(echo "something nasty")

Comments

0

Awk is able to get the variable in a POSIX compliant and safe way if the variable is an environment variable:

export a=var var=1
awk -v a="$a" 'BEGIN { print ENVIRON[a] }' -v a="$a"

Eval can also be guarded like this, which will strip forbidden characters:

export a=var var=1
eval "v=\$${a##*[!0-9_a-z_A-Z]*}"
echo "$v"  # shows '1'
export a='(touch hacked!)' var=1
eval "v=\$${a##*[!0-9_a-z_A-Z]*}"
echo "$v"  # shows '$'

2 Comments

It's very rarely useful to answer a question that is 14 years old and already has an accepted answer (with a very high upvote count, no less).
Is arbitrary code execution vulnerabilities an exception? If not, apologies for the noise.
0

I use a (relatively) short, POSIX compatible and execution safe way to assign a value:

echo -n "$var_name" | tr '\n' '#' | grep -Pq '^\w+$' &&
    eval "var_value=\$$var_name"

grep regex is very flexible and can be changed to be as strict/flexible as required for variable name validation.

The only downside is reliance on grep and coreutils (tr), but these utilities are guaranteed to be present in any Linux distribution minimal install.

Note. I only posted this answer because it is useful for everyone who write shell scripts on systems where /bin/sh is not a symlink to bash.

Comments

-1
VALUE=$(eval "echo \$$SOME_VAR_NAME")

Example

SSH_KEY_FILE_PATH_FOO="/tmp/key"
SSH_KEY_FILE_PATH_VAR_NAME_PREFIX="SSH_KEY_FILE_PATH"
SSH_KEY_FILE_PATH_VAR_NAME_SUFFIX="FOO"
SSH_KEY_FILE_PATH=$(eval "echo \$${SSH_KEY_FILE_PATH_VAR_NAME_PREFIX}_${SSH_KEY_FILE_PATH_VAR_NAME_SUFFIX}")
echo "$SSH_KEY_FILE_PATH"
/tmp/key

Comments

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.