[bash] Dynamic variable names in Bash

I am confused about a bash script.

I have the following code:

function grep_search() {
    magic_way_to_define_magic_variable_$1=`ls | tail -1`
    echo $magic_variable_$1
}

I want to be able to create a variable name containing the first argument of the command and bearing the value of e.g. the last line of ls.

So to illustrate what I want:

$ ls | tail -1
stack-overflow.txt

$ grep_search() open_box
stack-overflow.txt

So, how should I define/declare $magic_way_to_define_magic_variable_$1 and how should I call it within the script?

I have tried eval, ${...}, \$${...}, but I am still confused.

This question is related to bash variables dynamic syntax

The answer is


Example below returns value of $name_of_var

var=name_of_var
echo $(eval echo "\$$var")

for varname=$prefix_suffix format, just use:

varname=${prefix}_suffix

Beyond associative arrays, there are several ways of achieving dynamic variables in Bash. Note that all these techniques present risks, which are discussed at the end of this answer.

In the following examples I will assume that i=37 and that you want to alias the variable named var_37 whose initial value is lolilol.

Method 1. Using a “pointer” variable

You can simply store the name of the variable in an indirection variable, not unlike a C pointer. Bash then has a syntax for reading the aliased variable: ${!name} expands to the value of the variable whose name is the value of the variable name. You can think of it as a two-stage expansion: ${!name} expands to $var_37, which expands to lolilol.

name="var_$i"
echo "$name"         # outputs “var_37”
echo "${!name}"      # outputs “lolilol”
echo "${!name%lol}"  # outputs “loli”
# etc.

Unfortunately, there is no counterpart syntax for modifying the aliased variable. Instead, you can achieve assignment with one of the following tricks.

1a. Assigning with eval

eval is evil, but is also the simplest and most portable way of achieving our goal. You have to carefully escape the right-hand side of the assignment, as it will be evaluated twice. An easy and systematic way of doing this is to evaluate the right-hand side beforehand (or to use printf %q).

And you should check manually that the left-hand side is a valid variable name, or a name with index (what if it was evil_code # ?). By contrast, all other methods below enforce it automatically.

# check that name is a valid variable name:
# note: this code does not support variable_name[index]
shopt -s globasciiranges
[[ "$name" == [a-zA-Z_]*([a-zA-Z_0-9]) ]] || exit

value='babibab'
eval "$name"='$value'  # carefully escape the right-hand side!
echo "$var_37"  # outputs “babibab”

Downsides:

  • does not check the validity of the variable name.
  • eval is evil.
  • eval is evil.
  • eval is evil.

1b. Assigning with read

The read builtin lets you assign values to a variable of which you give the name, a fact which can be exploited in conjunction with here-strings:

IFS= read -r -d '' "$name" <<< 'babibab'
echo "$var_37"  # outputs “babibab\n”

The IFS part and the option -r make sure that the value is assigned as-is, while the option -d '' allows to assign multi-line values. Because of this last option, the command returns with an non-zero exit code.

Note that, since we are using a here-string, a newline character is appended to the value.

Downsides:

  • somewhat obscure;
  • returns with a non-zero exit code;
  • appends a newline to the value.

1c. Assigning with printf

Since Bash 3.1 (released 2005), the printf builtin can also assign its result to a variable whose name is given. By contrast with the previous solutions, it just works, no extra effort is needed to escape things, to prevent splitting and so on.

printf -v "$name" '%s' 'babibab'
echo "$var_37"  # outputs “babibab”

Downsides:

  • Less portable (but, well).

Method 2. Using a “reference” variable

Since Bash 4.3 (released 2014), the declare builtin has an option -n for creating a variable which is a “name reference” to another variable, much like C++ references. Just as in Method 1, the reference stores the name of the aliased variable, but each time the reference is accessed (either for reading or assigning), Bash automatically resolves the indirection.

In addition, Bash has a special and very confusing syntax for getting the value of the reference itself, judge by yourself: ${!ref}.

declare -n ref="var_$i"
echo "${!ref}"  # outputs “var_37”
echo "$ref"     # outputs “lolilol”
ref='babibab'
echo "$var_37"  # outputs “babibab”

This does not avoid the pitfalls explained below, but at least it makes the syntax straightforward.

Downsides:

  • Not portable.

Risks

All these aliasing techniques present several risks. The first one is executing arbitrary code each time you resolve the indirection (either for reading or for assigning). Indeed, instead of a scalar variable name, like var_37, you may as well alias an array subscript, like arr[42]. But Bash evaluates the contents of the square brackets each time it is needed, so aliasing arr[$(do_evil)] will have unexpected effects… As a consequence, only use these techniques when you control the provenance of the alias.

function guillemots() {
  declare -n var="$1"
  var="«${var}»"
}

arr=( aaa bbb ccc )
guillemots 'arr[1]'  # modifies the second cell of the array, as expected
guillemots 'arr[$(date>>date.out)1]'  # writes twice into date.out
            # (once when expanding var, once when assigning to it)

The second risk is creating a cyclic alias. As Bash variables are identified by their name and not by their scope, you may inadvertently create an alias to itself (while thinking it would alias a variable from an enclosing scope). This may happen in particular when using common variable names (like var). As a consequence, only use these techniques when you control the name of the aliased variable.

function guillemots() {
  # var is intended to be local to the function,
  # aliasing a variable which comes from outside
  declare -n var="$1"
  var="«${var}»"
}

var='lolilol'
guillemots var  # Bash warnings: “var: circular name reference”
echo "$var"     # outputs anything!

Source:


This will work too

my_country_code="green"
x="country"

eval z='$'my_"$x"_code
echo $z                 ## o/p: green

In your case

eval final_val='$'magic_way_to_define_magic_variable_"$1"
echo $final_val

As per BashFAQ/006, you can use read with here string syntax for assigning indirect variables:

function grep_search() {
  read "$1" <<<$(ls | tail -1);
}

Usage:

$ grep_search open_box
$ echo $open_box
stack-overflow.txt

Combining two highly rated answers here into a complete example that is hopefully useful and self-explanatory:

#!/bin/bash

intro="You know what,"
pet1="cat"
pet2="chicken"
pet3="cow"
pet4="dog"
pet5="pig"

# Setting and reading dynamic variables
for i in {1..5}; do
        pet="pet$i"
        declare "sentence$i=$intro I have a pet ${!pet} at home"
done

# Just reading dynamic variables
for i in {1..5}; do
        sentence="sentence$i"
        echo "${!sentence}"
done

echo
echo "Again, but reading regular variables:"
echo $sentence1
echo $sentence2
echo $sentence3
echo $sentence4
echo $sentence5

Output:

You know what, I have a pet cat at home
You know what, I have a pet chicken at home
You know what, I have a pet cow at home
You know what, I have a pet dog at home
You know what, I have a pet pig at home

Again, but reading regular variables:
You know what, I have a pet cat at home
You know what, I have a pet chicken at home
You know what, I have a pet cow at home
You know what, I have a pet dog at home
You know what, I have a pet pig at home


Even though it's an old question, I still had some hard time with fetching dynamic variables names, while avoiding the eval (evil) command.

Solved it with declare -n which creates a reference to a dynamic value, this is especially useful in CI/CD processes, where the required secret names of the CI/CD service are not known until runtime. Here's how:

# Bash v4.3+
# -----------------------------------------------------------
# Secerts in CI/CD service, injected as environment variables
# AWS_ACCESS_KEY_ID_DEV, AWS_SECRET_ACCESS_KEY_DEV
# AWS_ACCESS_KEY_ID_STG, AWS_SECRET_ACCESS_KEY_STG
# -----------------------------------------------------------
# Environment variables injected by CI/CD service
# BRANCH_NAME="DEV"
# -----------------------------------------------------------
declare -n _AWS_ACCESS_KEY_ID_REF=AWS_ACCESS_KEY_ID_${BRANCH_NAME}
declare -n _AWS_SECRET_ACCESS_KEY_REF=AWS_SECRET_ACCESS_KEY_${BRANCH_NAME}

export AWS_ACCESS_KEY_ID=${_AWS_ACCESS_KEY_ID_REF}
export AWS_SECRET_ACCESS_KEY=${_AWS_SECRET_ACCESS_KEY_REF}

echo $AWS_ACCESS_KEY_ID $AWS_SECRET_ACCESS_KEY
aws s3 ls

This should work:

function grep_search() {
    declare magic_variable_$1="$(ls | tail -1)"
    echo "$(tmpvar=magic_variable_$1 && echo ${!tmpvar})"
}
grep_search var  # calling grep_search with argument "var"

Use declare

There is no need on using prefixes like on other answers, neither arrays. Use just declare, double quotes, and parameter expansion.

I often use the following trick to parse argument lists contanining one to n arguments formatted as key=value otherkey=othervalue etc=etc, Like:

# brace expansion just to exemplify
for variable in {one=foo,two=bar,ninja=tip}
do
  declare "${variable%=*}=${variable#*=}"
done
echo $one $two $ninja 
# foo bar tip

But expanding the argv list like

for v in "$@"; do declare "${v%=*}=${v#*=}"; done

Extra tips

# parse argv's leading key=value parameters
for v in "$@"; do
  case "$v" in ?*=?*) declare "${v%=*}=${v#*=}";; *) break;; esac
done
# consume argv's leading key=value parameters
while (( $# )); do
  case "$v" in ?*=?*) declare "${v%=*}=${v#*=}";; *) break;; esac
  shift
done

For zsh (newers mac os versions), you should use

real_var="holaaaa"
aux_var="real_var"
echo ${(P)aux_var}
holaaaa

Instead of "!"


Wow, most of the syntax is horrible! Here is one solution with some simpler syntax if you need to indirectly reference arrays:

#!/bin/bash

foo_1=(fff ddd) ;
foo_2=(ggg ccc) ;

for i in 1 2 ;
do
    eval mine=( \${foo_$i[@]} ) ;
    echo ${mine[@]}" " ;
done ;

For simpler use cases I recommend the syntax described in the Advanced Bash-Scripting Guide.


I want to be able to create a variable name containing the first argument of the command

script.sh file:

#!/usr/bin/env bash
function grep_search() {
  eval $1=$(ls | tail -1)
}

Test:

$ source script.sh
$ grep_search open_box
$ echo $open_box
script.sh

As per help eval:

Execute arguments as a shell command.


You may also use Bash ${!var} indirect expansion, as already mentioned, however it doesn't support retrieving of array indices.


For further read or examples, check BashFAQ/006 about Indirection.

We are not aware of any trick that can duplicate that functionality in POSIX or Bourne shells without eval, which can be difficult to do securely. So, consider this a use at your own risk hack.

However, you should re-consider using indirection as per the following notes.

Normally, in bash scripting, you won't need indirect references at all. Generally, people look at this for a solution when they don't understand or know about Bash Arrays or haven't fully considered other Bash features such as functions.

Putting variable names or any other bash syntax inside parameters is frequently done incorrectly and in inappropriate situations to solve problems that have better solutions. It violates the separation between code and data, and as such puts you on a slippery slope toward bugs and security issues. Indirection can make your code less transparent and harder to follow.


An extra method that doesn't rely on which shell/bash version you have is by using envsubst. For example:

newvar=$(echo '$magic_variable_'"${dynamic_part}" | envsubst)

For indexed arrays, you can reference them like so:

foo=(a b c)
bar=(d e f)

for arr_var in 'foo' 'bar'; do
    declare -a 'arr=("${'"$arr_var"'[@]}")'
    # do something with $arr
    echo "\$$arr_var contains:"
    for char in "${arr[@]}"; do
        echo "$char"
    done
done

Associative arrays can be referenced similarly but need the -A switch on declare instead of -a.


I've been looking for better way of doing it recently. Associative array sounded like overkill for me. Look what I found:

suffix=bzz
declare prefix_$suffix=mystr

...and then...

varname=prefix_$suffix
echo ${!varname}

Examples related to bash

Comparing a variable with a string python not working when redirecting from bash script Zipping a file in bash fails How do I prevent Conda from activating the base environment by default? Get first line of a shell command's output Fixing a systemd service 203/EXEC failure (no such file or directory) /bin/sh: apt-get: not found VSCode Change Default Terminal Run bash command on jenkins pipeline How to check if the docker engine and a docker container are running? How to switch Python versions in Terminal?

Examples related to variables

When to create variables (memory management) How to print a Groovy variable in Jenkins? What does ${} (dollar sign and curly braces) mean in a string in Javascript? How to access global variables How to initialize a variable of date type in java? How to define a variable in a Dockerfile? Why does foo = filter(...) return a <filter object>, not a list? How can I pass variable to ansible playbook in the command line? How do I use this JavaScript variable in HTML? Static vs class functions/variables in Swift classes?

Examples related to dynamic

Please help me convert this script to a simple image slider Declare an empty two-dimensional array in Javascript? Compiling dynamic HTML strings from database Changing datagridview cell color dynamically What is the difference between dynamic programming and greedy approach? Dynamic variable names in Bash Dynamically Add C# Properties at Runtime How to generate a HTML page dynamically using PHP? Change UITableView height dynamically How to create own dynamic type or dynamic object in C#?

Examples related to syntax

What is the 'open' keyword in Swift? Check if returned value is not null and if so assign it, in one line, with one method call Inline for loop What does %>% function mean in R? R - " missing value where TRUE/FALSE needed " Printing variables in Python 3.4 How to replace multiple patterns at once with sed? What's the meaning of "=>" (an arrow formed from equals & greater than) in JavaScript? How can I fix MySQL error #1064? What do >> and << mean in Python?