34

I want to dynamically create a sequence of strings by manipulate an array of elements and create some arithmetic procedure.

for name in FIRST SECOND THIRD FOURTH FIFTH; do
    $name = $(( $6 + 1 ))
    $name = "${$name}q;d"
    echo "${$name}"; printf "\n"
done

The desire outcome would be the below for $6 equals 0.

1q;d
2q;d
3q;d
4q;d
5q;d

But I get this error

reel_first_part.sh: line 18: FIRST: command not found
reel_first_part.sh: line 19: ${$name}q;d: bad substitution
reel_first_part.sh: line 18: FIRST: command not found
reel_first_part.sh: line 19: ${$name}q;d: bad substitution
reel_first_part.sh: line 18: FIRST: command not found
reel_first_part.sh: line 19: ${$name}q;d: bad substitution

I guess it's something simple. It used to work when I did something like

FIRST=$(( $6 + 1 ))
FIRST="${FIRST}q;d"
3
  • 1
    Can you explain it little better. Don't really understand what are you trying to do.
    – neuron
    Commented Aug 11, 2015 at 11:31
  • What is ` $name = $(( $6 + 1 ))` supposed to do? Commented Aug 11, 2015 at 11:48
  • @PSkocik I was hoping to do FIRST=$(( $6 + 1 )) Commented Aug 11, 2015 at 11:58

7 Answers 7

41

If you want to reference a bash variable while having the name stored in another variable you can do it as follows:

$ var1=hello
$ var2=var1
$ echo ${!var2}
hello

You store the name of the variable you want to access in, say, var2 in this case. Then you access it with ${!<varable name>} where <variable name> is a variable holding the name of the variable you want to access.

5
  • 3
    There is portable way with eval var=\$$holder but eval is dangerous!
    – gavenkoa
    Commented Aug 6, 2018 at 22:01
  • 1
    Thank you very much, and +1. This should be the accepted answer, since it does not use eval and since the OP specifically has asked for bash, so portability is not an issue. However, to read more details about it in the bash manual and to be able to find the respective section, how is this construct / method called?
    – Binarus
    Commented Aug 27, 2020 at 10:19
  • Great answer! Upvoted. I expounded upon your answer with additional insight and explanation, and added an "equivalent" example using the "evil" eval, in my answer I just added here: unix.stackexchange.com/questions/222487/…. Commented Jan 30, 2021 at 6:49
  • 1
    The ${!var_name} doesn't work in BusyBox ash and dash so the eval var_val=\$$var_name is the only cross platform syntax :( Commented Nov 18, 2023 at 14:29
  • 1
    @SergeyPonomarev Sure, but those are not bash, and this question specifically asks about bash. Using eval will always be risky if processing user supplied values, but perhaps there are situations where that will be a suitable approach Commented Nov 20, 2023 at 2:47
21

First of all there can not be any space around = in variable declaration in bash.

To get what you want you can use eval.

For example a sample script like yours :

#!/bin/bash
i=0
for name in FIRST SECOND THIRD FOURTH FIFTH; do
    eval "$name"="'$(( $i + 1 ))q;d'"
    printf '%s\n' "${!name}"
    i=$(( $i + 1 ))
done

Prints :

1q;d
2q;d
3q;d
4q;d
5q;d

Use eval cautiously, some people call it evil for some valid reason.

declare would work too :

#!/bin/bash
i=0
for name in FIRST SECOND THIRD FOURTH FIFTH; do
    declare "$name"="$(( $i + 1 ))q;d"
    printf '%s\n' "${!name}"
    i=$(( $i + 1 ))
done

also prints :

1q;d
2q;d
3q;d
4q;d
5q;d
6
  • What is the ! exclamation mark printf '%s\n' "${!name}" for? Commented Aug 11, 2015 at 12:24
  • 2
    Its called indirect expansion of bash parameter expansion..read this
    – heemayl
    Commented Aug 11, 2015 at 12:27
  • 2
    Bash also has a nicer alternative to declare / eval: printf -v varname '%fmt' args. Some bash-completion internal functions use this for call-by-reference. (pass the name of a variable to store into). Commented Aug 11, 2015 at 18:25
  • Note: Using declare only sets the variable in the local scope, while the eval approach sets it globally.
    – user
    Commented Jun 27, 2019 at 3:21
  • 1
    @user declare has flag -g to declare variable to be a global.
    – Smar
    Commented May 11, 2021 at 8:12
10

I just want to show a slightly more-thorough version of Eric Renouf's answer to make it crystal clear how you can dynamically generate a variable name from multiple other variables and then access the contents of that new, dynamically-generated variable.

some_variable="Hey how are you?"

# the 1st half of the variable name "some_variable"
var1="some"
# the 2nd half of the variable name
var2="variable"

# dynamically recreate the variable name "some_variable", stored
# as a string inside variable `var3`
var3="${var1}_${var2}"

Now look at these outputs:

echo "${var3}"

outputs:

some_variable

BUT this (the exact same as above except we added the ! char just before the variable name is all):

echo "${!var3}"

outputs:

Hey how are you?

It's magical!

It's as though we had a C macro where we had echo "${${var3}}" which expanded to echo "${some_variable}" which then became Hey how are you?.

That syntax would be invalid, however, and you'd get this error:

$ echo "${${var3}}"
bash: ${${var3}}: bad substitution

You can do this echo "${!var3}" trick in place of using eval, since eval is considered "dangerous" and "evil". For understanding and education though, here is the equivalent way to do this with eval instead:

$ eval echo "\$${var3}"
Hey how are you?

versus:

$ echo "${!var3}"
Hey how are you?

eval echo "\$${var3}" expands to eval echo "$some_variable", which has the same output:

$ eval echo "$some_variable"
Hey how are you?

But, even though eval echo "\$${var3}" and echo "${!var3}" produce the exact same result in this case (the Hey how are you? output), apparently the eval version is evil and the ! version is good.

You can read the "good" version (echo "${!var3}") as follows (in my own words):

echoing "$var3" means: "output the contents of the var3 variable." BUT, echoing "${!var3}" means: "output the contents of what the var3 variable contains, assuming its contents are the name to another variable!"

The ! in bash here is kind of like dereferencing a pointer in C with the * character like this!:

*some_ptr

It adds an extra layer of abstraction.

Related

  1. Note that the need for this ! trick can frequently be avoided using associative arrays in bash. Associative arrays are essentially "hash tables", which are called "unordered maps" in C++ and "dicts" (dictionaries) in Python. Here are a few relevant links on them:

    1. https://stackoverflow.com/questions/6149679/multidimensional-associative-arrays-in-bash
    2. [VERY GOOD TUTORIAL!] *****https://www.artificialworlds.net/blog/2012/10/17/bash-associative-array-examples/

    Keep in mind, however, that associative arrays (and all other arrays too) in bash are 1-dimensional! From man 1 bash (emphasis added):

    Arrays

    Bash provides one-dimensional indexed and associative array variables. Any variable may be used as an indexed array; the declare builtin will explicitly declare an array. There is no maximum limit on the size of an array, nor any requirement that members be indexed or assigned contiguously. Indexed arrays are referenced using integers (including arithmetic expressions) and are zero-based; associative arrays are referenced using arbitrary strings. Unless otherwise noted, indexed array indices must be non-negative integers.

    An indexed array is created automatically if any variable is assigned to using the syntax name[subscript]=value. The subscript is treated as an arithmetic expression that must eval‐ uate to a number. To explicitly declare an indexed array, use declare -a name (see SHELL BUILTIN COMMANDS below). declare -a name[subscript] is also accepted; the subscript is ignored.

    Associative arrays are created using declare -A name.

    Attributes may be specified for an array variable using the declare and readonly builtins. Each attribute applies to all members of an array.

    Arrays are assigned to using compound assignments of the form name=(value1 ... valuen), where each value is of the form [subscript]=string. Indexed array assignments do not require anything but string. When assigning to indexed arrays, if the optional brackets and subscript are supplied, that index is assigned to; otherwise the index of the element assigned is the last index assigned to by the statement plus one. Indexing starts at zero.

    When assigning to an associative array, the subscript is required.

4
  • Which approach would you recommend with shell? As ${!<varable name>} is not supported by shell.
    – Freddy
    Commented Jan 16, 2023 at 12:53
  • 1
    @Freddy, eval echo "\$${var3}" works fine in the sh shell. Or, does your system have bash? Type in bash --version at the command-line. If it returns with a version number, you have bash. Add #!/usr/bin/env bash to the top of your bash script, like I do in eRCaGuy_hello_world/bash/hello_world_basic.sh, to force that script to use bash, and then use echo "${!var3}" instead. Commented Jan 16, 2023 at 16:28
  • I though maybe there is a way to avoid eval in sh shell but then eval echo "\$${var3}" it is.
    – Freddy
    Commented Jan 18, 2023 at 22:08
  • 1
    @Freddy, if you post it as a question, put a link back here so I can follow the question too, but I don't think there's another way in sh, but I'm no expert on the matter. Commented Jan 18, 2023 at 22:14
2

What I get from your code and your desired output (correct me if I'm wrong):
There is no use of the "FIRST"/"SECOND"/... variable names, you just need a loop with an index...

This will do the job:

for i in {1..5} ; do echo $i"q;d" ; done

3
  • Yes, you are right, except additionally I want to do an arithmetic function with a variable. Commented Aug 11, 2015 at 11:55
  • Can you give an example of this arithmetic function? Do you need the variable name (like "THIRD") for it or just the index value?
    – csny
    Commented Aug 11, 2015 at 12:01
  • SUM=$(($6 + $i)); echo $SUM"q;d", I see what I was doing wrong. Commented Aug 11, 2015 at 12:09
1
index=0;                                                                                                                                                                                                           
for name in FIRST SECOND THIRD FOURTH FIFTH; do
    name=$(($index + 1))
    echo "${name}q;d"
    index=$((index+1))
done

Is that what you are trying?

1

I'm adding a response here as I don't think the most upvoted solution really represents a correct answer to the problem, and the accepted solution could lead to unexpected behavior, e.g.

desired_var_name="echo hello ace! && x"
eval $desired_var_name=value

You should use the declare command, e.g.

desired_var_name="myvar"
desired_value=assiged
declare $desired_var_name=$desired_value

Now,

echo $myvar

outputs

assiged
2
  • 1
    Well, you have presented an example of how eval can be dangerous.  The accepted answer says that eval is dangerous, and this is documented widely, along with the warning that you shouldn’t, for example, allow a (possibly hostile or malicious) user specify a string that will be used in an eval statement without checking it thoroughly. The accepted answer also shows declare, so you don’t seem to be adding a lot of new information. Commented Jul 15, 2022 at 19:07
  • This answer helped me more than the accepted answer because of its simplicity.
    – Kay
    Commented Aug 5, 2022 at 14:21
0

I develop a text based non-interactive randomized fighting game script (where the fight plays out like an old style MUD). My latest innovation is to use arrays to store the active fighters and or items during a battle. This is the method I worked out to create and utilize dynamic arrays that can be referenced from the main 'actors' array. The following is my proof of concept code that I've been working out. The part that may help you is down where the arrays get created and populated:

    # action line:
my_name (eg. johnny cage) uses obj_name (eg. duck)

    # 'duck' item details:
obj=duck.s.2.3
obj_name=$(echo "$item" | cut -d. -f1)
obj_type=$(echo "$item" | cut -d. -f2)
obj_min=$(echo "$item" | cut -d. -f3)
obj_max=$(echo "$item" | cut -d. -f4)
if obj_type="a" (actor) then they can use items:
obj_useitems=1
else it cant use items:
obj_useitems=0
obj_owner="$my_name"
if obj_name="duck" then set a delay before it attacks:
obj_delay=$(( (RANDOM % 6) + 6 ))
obj_id=$(( ${#actors[@]} + 1 ))

    # if it's an actor (eg. if obj_name="duck" or obj_type="a"), add to the array of actors that exist:
actors+=( "$obj_name.$obj_id" )

    # create the object array for the item / actor with all its/their stats:
declare -A new_obj=( [name]=$obj_name [type]=$obj_type [min]=$obj_min [max]=$obj_max [id]=$obj_id [use items]=$obj_useitems [owner]=$obj_owner [active]=$obj_delay )
    # create the dynamic named array with the object's id:
declare -A obj_$obj_id
    # assign the new_obj values to the dynamic array:
    eval obj_$obj_id[name]="\${new_obj[name]}"; eval obj_$obj_id[type]="\${new_obj[type]}"; eval obj_$obj_id[min]="\${new_obj[min]}"
    eval obj_$obj_id[max]="\${new_obj[max]}";eval obj_$obj_id[id]="\${new_obj[id]}";eval obj_$obj_id[life]="\${new_obj[life]}"
    eval obj_$obj_id[use items]="\${new_obj[use items]}"; eval obj_$obj_id[owner]="\${new_obj[owner]}"; eval obj_$obj_id[active]="\${new_obj[active]}"
    
# loop through list of actors, and retrieve values from their associated dynamic array that contains all of their stats:

for actor in "${actors[@]}"; do
    actor_name=$(echo "$actor" | cut -d. -f1)
    actor_id=$(echo "$actor" | cut -d. -f2)
    if [[ $(eval echo \${obj_$actor_id[type]}) = "s" ]]; then echo yes; else echo no; fi
done

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .