22

I have two arrays:

arrayA=(1 2 3)
arrayB=(a b c)

and I want to print out one of them using a command line argument, i.e., without any if else.

I tried a few variations on the syntax with no success. I am wanting to do something like this:

ARG="$1"

echo ${array${ARG}[@]}

but I get a "bad substitution" error. How can I achieve this?

Aaron
  • 1,507

8 Answers8

34

Try doing this :

$ arrayA=(1 2 3)
$ x=A
$ var=array$x[@]
$ echo ${!var}
1 2 3

NOTE

  • from man bash (parameter expansion) :
    ${parameter}
           The value of parameter is substituted.
 The braces are required when parameter is a positional parameter with
  more than one

digit, or when parameter is followed by a character which is not to be interpreted as part of its name.
* If the first character of parameter is an exclamation point (!), a level of variable indirection is introduced. 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. This is known as indirect expansion. * The exceptions to this are the expansions of ${!prefix*} and ${!name[@]} described below. The exclamation point must immediately follow the left brace in order to introduce indirection.

11

While you can use the indirect access as pointed in another answer, another way (in ksh and Bash 4.3 and newer) would be to use namerefs. Especially in the case of arrays this may be more useful since you can index the array through the nameref and don't need to put the index in the variable used as the reference.

arr1=(a b c)
arr2=(x y z)
typeset -n p=arr1    # or 'declare -n' 
echo "${p[1]}"       # prints 'b'

This doesn't work through the indirect access:

q=arr2
echo "${!q}"         # prints 'x', the same as $arr2
echo "${!q[1]}"      # doesn't work, it tries to take q[1] as a reference

As a C programmer might put it, ${!q[1]} here acts as if q was an array of pointers, instead of being a pointer to an array.

ilkkachu
  • 138,973
3

This took a lot of trial and error but eventually worked.

I took some inspiration from Youness. But all other answers did not help on my old bash (suse11sp1[3.2.51(1)-release])

The 'for' loop refused to expand the indirect array, instead you need to pre-expand it, use that to create another array with your new variable name. My example below shows a double loop, as that is my intended use.

THEBIGLOOP=(New_FOO New_BAR)

FOOthings=(1 2 3)
BARthings=(a b c)

for j in ${THEBIGLOOP[*]}
do
    TheNewVariable=$(eval echo \${${j#New_}things[@]})

    for i in $TheNewVariable
        do
            echo  $j $i" hello"
        echo
    done
done

I'm using # to delete the "New_" from the first array entry, then concatenating with "things", to get "FOOthings". \${} with echo and eval, then do their thing in order without throwing errors, which is wrapped in a new $() and assigned the new variable name.

$ Test.sh

New_FOO 1 hello

New_FOO 2 hello

New_FOO 3 hello

New_BAR a hello

New_BAR b hello

New_BAR c hello

UPDATE ##### 2018/06/07

I've recently discovered one more spin on this issue. The variable created is not actually an array, but a space delimited string. For the task above this was ok, because of how "for" works, it doesn't read the array, it is expanded and then looped through, see extract below:

for VARIABLE in 1 2 3 4 5 .. N
do
    command1
    command2
    commandN
done

But, I then needed to use it as an array. For this I needed to perform one more step. I took code verbatim by Dennis Williamson. I've tested it and it works fine.

IFS=', ' read -r -a TheNewVariable <<< ${TheNewVariable[@]}

The "IFS=', '" is a variable containing your deliminator. "read" with "-a" cuts and feeds the sting back into the array variable. Note, this has no respect for quotation marks, but there are a few options in read to manage this, e.g. I've removed the -r flag which I didn't need. So I have now combined this addition in the variable creation, which allows the data to be treated and addressed as it should.

THEBIGLOOP=(New_FOO New_BAR)

FOOthings=(1 2 3)
BARthings=(a b c)

for j in ${THEBIGLOOP[*]}
do

    IFS=', ' read -a TheNewVariable <<< $(eval echo \${${j#New_}things[@]})

    for i in ${TheNewVariable[@]}  #Now have to wrap with {} and expand with @
        do
            echo  $j $i" hello"
            echo  ${TheNewVariable[$i]}  #This would not work in the original code
        echo
    done
done
  • "for": it doesn't work with array elements having spaces; still, this is useful – Adrian Sep 02 '20 at 13:12
  • @adrhc As this is for building variable names, you should never use array elements with spaces. Do you have a different use case in mind? – Stripy42 Sep 13 '20 at 19:28
  • It won't work with array elements containing spaces, e.g.: DIRS_TO_SYNC_AP77G=("/DVDs Nunta D&A part 2" "/FILME cu Ami & Nataly RAW" "/FILME de familie RAW") – Adrian Dec 31 '21 at 14:26
  • @adrhc I don't know of any language let along bash that would accept a variable with a space, why would you want that? Are you just trying to get just DVD or FILME as the array name? Perhaps escape the spaces in the array, feed the required item though SED or AWK to get the bit you want first. And then feed into the next step.

    Media=$(echo ${theLoopsVariable} | awk '{print $1}' IFS=', ' read -a TheNewVariable <<< $(eval echo ${${Media#New_}things[@]})

    – Stripy42 Jan 19 '22 at 10:01
  • My formatting went a bit wrong there: Media=$(echo ${theLoopsVariable} | awk '{print $1}' IFS=', ' read -a TheNewVariable <<< $(eval echo \${${Media#New_}things[@]}) – Stripy42 Jan 19 '22 at 10:09
2
arrayA=(1 2 3)
arrayB=(a b c)

ARG="$1"

eval echo \${array${ARG}[@]}

dataget (){ 
    eval echo \${array${1}[${2:-@}]}
}
$ dataget A
1 2 3
$ dataget A 0
1
$ dataget B 1
b

note: escap cotes in case of space!

eval dostuff \"\${array${1}[${2:-@}]}\"
Yunus
  • 1,684
1

This is how you would create a dynamically named variable (bash version < 4.3).

# Dynamically named array
my_variable_name="dyn_arr_names"
eval $my_variable_name=\(\)

# Adding by index to the array eg. dyn_arr_names[0]="bob"
eval $my_variable_name[0]="bob"

# Adding by pushing onto the array eg. dyn_arr_names+=(robert)
eval $my_variable_name+=\(robert\)

# Print value stored at index indirect
echo ${!my_variable_name[0]}

# Print value stored at index
eval echo \${$my_variable_name[0]}

# Get item count
eval echo \${#$my_variable_name[@]}

Below is a group of functions that can be used to manage dynamically named arrays (bash version < 4.3).

# Dynamically create an array by name
function arr() {
    [[ ! "$1" =~ ^[a-zA-Z_]+[a-zA-Z0-9_]*$ ]] && { echo "Invalid bash variable" 1>&2 ; return 1 ; }
     # The following line can be replaced with 'declare -ag $1=\(\)'
     # Note: For some reason when using 'declare -ag $1' without the parentheses will make 'declare -p' fail
    eval $1=\(\)
}

# Insert incrementing by incrementing index eg. array+=(data)
function arr_insert() { 
    [[ ! "$1" =~ ^[a-zA-Z_]+[a-zA-Z0-9_]*$ ]] && { echo "Invalid bash variable" 1>&2 ; return 1 ; }
    declare -p "$1" > /dev/null 2>&1
    [[ $? -eq 1 ]] && { echo "Bash variable [${1}] doesn't exist" 1>&2 ; return 1 ; }
    eval $1[\$\(\(\${#${1}[@]}\)\)]=\$2
}

# Update an index by position
function arr_set() {
    [[ ! "$1" =~ ^[a-zA-Z_]+[a-zA-Z0-9_]*$ ]] && { echo "Invalid bash variable" 1>&2 ; return 1 ; }
    declare -p "$1" > /dev/null 2>&1
    [[ $? -eq 1 ]] && { echo "Bash variable [${1}] doesn't exist" 1>&2 ; return 1 ; }
    eval ${1}[${2}]=\${3}
}

# Get the array content ${array[@]}
function arr_get() {
    [[ ! "$1" =~ ^[a-zA-Z_]+[a-zA-Z0-9_]*$ ]] && { echo "Invalid bash variable" 1>&2 ; return 1 ; }
    declare -p "$1" > /dev/null 2>&1
    [[ $? -eq 1 ]] && { echo "Bash variable [${1}] doesn't exist" 1>&2 ; return 1 ; }
    eval echo \${${1}[@]}
}

# Get the value stored at a specific index eg. ${array[0]}  
function arr_at() {
    [[ ! "$1" =~ ^[a-zA-Z_]+[a-zA-Z0-9_]*$ ]] && { echo "Invalid bash variable" 1>&2 ; return 1 ; }
    declare -p "$1" > /dev/null 2>&1
    [[ $? -eq 1 ]] && { echo "Bash variable [${1}] doesn't exist" 1>&2 ; return 1 ; }
    [[ ! "$2" =~ ^(0|[-]?[1-9]+[0-9]*)$ ]] && { echo "Array index must be a number" 1>&2 ; return 1 ; }
    local v=$1
    local i=$2
    local max=$(eval echo \${\#${1}[@]})
    # Array has items and index is in range
    if [[ $max -gt 0 && $i -ge 0 && $i -lt $max ]]
    then 
        eval echo \${$v[$i]}
    fi
}

# Get the value stored at a specific index eg. ${array[0]}  
function arr_count() {
    [[ ! "$1" =~ ^[a-zA-Z_]+[a-zA-Z0-9_]*$ ]] && { echo "Invalid bash variable " 1>&2 ; return 1 ; }
    declare -p "$1" > /dev/null 2>&1
    [[ $? -eq 1 ]] && { echo "Bash variable [${1}] doesn't exist" 1>&2 ; return 1 ; }
    local v=${1}
    eval echo \${\#${1}[@]}
}



array_names=(bob jane dick)

for name in "${array_names[@]}"
do
    arr dyn_$name
done

echo "Arrays Created"
declare -a | grep "a dyn_"

# Insert three items per array
for name in "${array_names[@]}"
do
    echo "Inserting dyn_$name abc"
    arr_insert dyn_$name "abc"
    echo "Inserting dyn_$name def"
    arr_insert dyn_$name "def"
    echo "Inserting dyn_$name ghi"
    arr_insert dyn_$name "ghi"
done

for name in "${array_names[@]}"
do
    echo "Setting dyn_$name[0]=first"
    arr_set dyn_$name 0 "first"
    echo "Setting dyn_$name[2]=third"
    arr_set dyn_$name 2 "third"
done 

declare -a | grep "a dyn_"

for name in "${array_names[@]}"
do
    arr_get dyn_$name
done


for name in "${array_names[@]}"
do
    echo "Dumping dyn_$name by index"
    # Print by index
    for (( i=0 ; i < $(arr_count dyn_$name) ; i++ ))
    do
        echo "dyn_$name[$i]: $(arr_at dyn_$name $i)"

    done
done

for name in "${array_names[@]}"
do
    echo "Dumping dyn_$name"
    for n in $(arr_get dyn_$name)
    do
        echo $n
    done
done

Below is a group of functions that can be used to manage dynamically named arrays (bash version >= 4.3).

# Dynamically create an array by name
function arr() {
    [[ ! "$1" =~ ^[a-zA-Z_]+[a-zA-Z0-9_]*$ ]] && { echo "Invalid bash variable" 1>&2 ; return 1 ; }
    declare -g -a $1=\(\)   
}

# Insert incrementing by incrementing index eg. array+=(data)
function arr_insert() { 
    [[ ! "$1" =~ ^[a-zA-Z_]+[a-zA-Z0-9_]*$ ]] && { echo "Invalid bash variable" 1>&2 ; return 1 ; }
    declare -p "$1" > /dev/null 2>&1
    [[ $? -eq 1 ]] && { echo "Bash variable [${1}] doesn't exist" 1>&2 ; return 1 ; }
    declare -n r=$1
    r[${#r[@]}]=$2
}

# Update an index by position
function arr_set() {
    [[ ! "$1" =~ ^[a-zA-Z_]+[a-zA-Z0-9_]*$ ]] && { echo "Invalid bash variable" 1>&2 ; return 1 ; }
    declare -p "$1" > /dev/null 2>&1
    [[ $? -eq 1 ]] && { echo "Bash variable [${1}] doesn't exist" 1>&2 ; return 1 ; }
    declare -n r=$1 
    r[$2]=$3
}

# Get the array content ${array[@]}
function arr_get() {
    [[ ! "$1" =~ ^[a-zA-Z_]+[a-zA-Z0-9_]*$ ]] && { echo "Invalid bash variable" 1>&2 ; return 1 ; }
    declare -p "$1" > /dev/null 2>&1
    [[ $? -eq 1 ]] && { echo "Bash variable [${1}] doesn't exist" 1>&2 ; return 1 ; }
    declare -n r=$1 
    echo ${r[@]}
}

# Get the value stored at a specific index eg. ${array[0]}  
function arr_at() {
    [[ ! "$1" =~ ^[a-zA-Z_]+[a-zA-Z0-9_]*$ ]] && { echo "Invalid bash variable" 1>&2 ; return 1 ; }
    declare -p "$1" > /dev/null 2>&1
    [[ $? -eq 1 ]] && { echo "Bash variable [${1}] doesn't exist" 1>&2 ; return 1 ; }
    [[ ! "$2" =~ ^(0|[-]?[1-9]+[0-9]*)$ ]] && { echo "Array index must be a number" 1>&2 ; return 1 ; }
    declare -n r=$1 
    local max=${#r[@]}
    # Array has items and index is in range
    if [[ $max -gt 0 && $i -ge 0 && $i -lt $max ]]
    then 
        echo ${r[$2]}
    fi
}

# Get the value stored at a specific index eg. ${array[0]}  
function arr_count() {
    [[ ! "$1" =~ ^[a-zA-Z_]+[a-zA-Z0-9_]*$ ]] && { echo "Invalid bash variable " 1>&2 ; return 1 ; }
    declare -p "$1" > /dev/null 2>&1
    [[ $? -eq 1 ]] && { echo "Bash variable [${1}] doesn't exist" 1>&2 ; return 1 ; }
    declare -n r=$1
    echo ${#r[@]}
}



array_names=(bob jane dick)

for name in "${array_names[@]}"
do
    arr dyn_$name
done

echo "Arrays Created"
declare -a | grep "a dyn_"

# Insert three items per array
for name in "${array_names[@]}"
do
    echo "Inserting dyn_$name abc"
    arr_insert dyn_$name "abc"
    echo "Inserting dyn_$name def"
    arr_insert dyn_$name "def"
    echo "Inserting dyn_$name ghi"
    arr_insert dyn_$name "ghi"
done

for name in "${array_names[@]}"
do
    echo "Setting dyn_$name[0]=first"
    arr_set dyn_$name 0 "first"
    echo "Setting dyn_$name[2]=third"
    arr_set dyn_$name 2 "third"
done 

declare -a | grep 'a dyn_'

for name in "${array_names[@]}"
do
    arr_get dyn_$name
done


for name in "${array_names[@]}"
do
    echo "Dumping dyn_$name by index"
    # Print by index
    for (( i=0 ; i < $(arr_count dyn_$name) ; i++ ))
    do
        echo "dyn_$name[$i]: $(arr_at dyn_$name $i)"

    done
done

for name in "${array_names[@]}"
do
    echo "Dumping dyn_$name"
    for n in $(arr_get dyn_$name)
    do
        echo $n
    done
done

For more details on these examples visit Getting Bashed by Dynamic Arrays by Ludvik Jerabek

NOPx90
  • 21
  • 1
    I am curious why this gets downvoted. Is there something wrong/dangerous with the approach. Would like to use functions on bash<4.3. – stephenmm Oct 18 '19 at 17:30
  • I appreciate your post, i found useful info and specially the "Get item count" instruction: eval echo ${#$my_variable_name[@]} – Daniel Perez Apr 05 '20 at 20:38
  • "for": it doesn't work with array elements having spaces; still, this is useful – Adrian Sep 02 '20 at 13:12
  • Have you tried to modify $IFS? This is common when dealing with a for loop and values have spaces. – NOPx90 May 05 '22 at 20:08
0

This worked for me:

I have arrays declared as global from the form:

declare -a total_count
declare -a total_min
declare -a total_max
declare -a total_med
declare -a total_avg
declare -a total_stdev

And I want to to print table that is made of the content of the above arrays without having to do same work over and over.

I want the function to get the prefix of the array name and the number of rows I want to print and do the rest. Main challenge here was to access bash array at specific index with array name made of function variable.

I noticed that use of eval twice can do the work. Without this trick I saw 'bad substitution' error.

function print_table {
    name=$1
    len=$2

    # print banner
    echo -n "filename "
    for col in _count _min _max _med _avg _stdev
    do
        echo -n "${name}${col} "
    done
    echo ""

    #print data
    for ((i=0;i<len;i++)); do
        line="count=\$\{${name}_count[$i]\}"
        eval "$line"
        line="min=\$\{${name}_min[$i]\}"
        eval "$line"
        line="max=\$\{${name}_max[$i]\}"
        eval "$line"
        line="med=\$\{${name}_med[$i]\}"
        eval "$line"
        line="avg=\$\{${name}_avg[$i]\}"
        eval "$line"
        line="stdev=\$\{${name}_stdev[$i]\}"
        eval "$line"
        eval "printf \"%s %d %f %f %f %f %f \n\" \"${filename[$i]}\" \"$count\" \"$min\" \"$max\" \"$med\" \"$avg\" \"$stdev\" "
    done
}

AdminBee
  • 22,803
-1

Use eval

arrayA=(1 2 3)
ARG=arrayA
eval echo \${$ARG[@]} # equivalent to eval echo \${arrayA[@]}
                      # note that we escape the first '$' to prevent from 
                      # its parameter expansion before passing it to echo
MS.Kim
  • 1,687
  • 4
  • 18
  • 18
-1

no way :(

if your arrays are that simple, then use associative arrays

    declare -A array
    array[A]="1 2 3"
    array[B]="a b c"

unfortunately, if your arrays are more complicated ( for example array=( "a b" c ) ), that wouldn't work. Then, you need to think harder about another way to reach your goal.

watael
  • 911
  • What is the reason for the downvote? The associative array provides a nice way of grouping everything, assuming that my elements will all contain no space. – Aaron Jan 07 '13 at 23:45
  • 2
    @Aaron Assuming your elements don't contain spaces, that is a reasonable design. @watael I guess beginning the answer with “no way” when the primary focus of your question is clearly possible wasn't a good idea. – Gilles 'SO- stop being evil' Jan 08 '13 at 00:12