10

The Bash man page describes use of ${!a} to return the contents of the variable whose name is the contents of a (a level of indirection).

I'd like to know how to return all elements in an array using this, i.e.,

a=(one two three)
echo ${a[*]}

returns

one two three

I would like for:

b=a
echo ${!b[*]}

to return the same. Unfortunately, it doesn't, but returns 0 instead.

Update

Given the replies, I now realise that my example was too simple, since of course, something like:

b=("${a[@]}")

Will achieve exactly what I said I needed.

So, here's what I was trying to do:

LIST_lys=(lys1 lys2)
LIST_diaspar=(diaspar1 diaspar2)

whichone=$1   # 'lys' or 'diaspar'

_LIST=LIST_$whichone
LIST=${!_LIST[*]}

Of course, carefully reading the Bash man page shows that this won't work as expected because the last line simply returns the indices of the "array" $_LIST (not an array at all).

In any case, the following should do the job (as pointed out):

LIST=($(eval echo \${$_LIST[*]}))

or ... (the route that I went, eventually):

LIST_lys="lys1 lys2"
...
LIST=(${!_LIST})

Assuming, of course, that elements don't contain whitespace.

1
  • Add [@] to the pointer _LIST="LIST_${whichone}[@]" and then, use LIST=("${!_LIST}") to copy the array. It is a good idea to use lower case variable names to avoid conflicts with environment variables (All caps).
    – user232326
    Commented Mar 9, 2017 at 2:52

4 Answers 4

9

I think the use of indirect reference of bash variable should be treated literally.

Eg. For your original example:

a=(one two three)
echo ${a[*]} # one two three
b=a
echo ${!b[*]} # this would not work, because this notation 
              # gives the indices of the variable b which
              # is a string in this case and could be thought
              # as a array that conatins only one element, so
              # we get 0 which means the first element
c='a[*]'
echo ${!c} # this will do exactly what you want in the first
           # place

For the last real scenario, I believe the code below would do the work.

LIST_lys=(lys1 lys2)
LIST_diaspar=(diaspar1 diaspar2)

whichone=$1   # 'lys' or 'diaspar'

_LIST="LIST_$whichone"[*]
LIST=( "${!_LIST}" ) # Of course for indexed array only 
                     # and not a sparse one

It is better to use notation "${var[@]}" which avoid messing up with the $IFS and parameter expansion. Here is the final code.

LIST_lys=(lys1 lys2)
LIST_diaspar=(diaspar1 diaspar2)

whichone=$1   # 'lys' or 'diaspar'

_LIST="LIST_$whichone"[@]
LIST=( "${!_LIST}" ) # Of course for indexed array only 
                     # and not a sparse one
                     # It is essential to have ${!_LIST} quoted
8

You need to copy the elements explicitly. For an indexed array:

b=("${a[@]}")

For an associative array (note that a is the name of the array variable, not a variable whose value is the name of an array variable):

typeset -A b
for k in "${!a[@]}"; do b[$k]=${a[$k]}; done

If you have the variable name in an array, you can use the element-by-element method with an extra step to retrieve the keys.

eval "keys=(\"\${!$name[@]}\")"
for k in "${keys[@]}"; do eval "b[\$k]=\${$name[\$k]}"; done

(Warning, the code in this post was typed directly in a browser and not tested.)

3
  • You're quite right, but unfortunately, that doesn't solve my problem (my example was too simplistic). I've provided an update to be more clear on my intention.
    – Eric Smith
    Commented Sep 6, 2011 at 8:52
  • @Eric I think in ksh/bash you need eval at that stage. See my edit. Commented Sep 6, 2011 at 10:21
  • +1, but as per your disclaimer, this actually doesn't quite work (the last bit of code), but only needs a few minor adjustments. Specifically, it should be \${!$name[@]} on the first line, so that the first expansion is only of '$name', and the ${!a[@]} is saved for the eval, and the same thing in the for loop, with \${$name}. The other two backslashes before '$k' aren't strictly necessary there either.
    – krb686
    Commented Oct 30, 2016 at 23:45
2

${!b[*]} expands to the indices used in array b.

What you would like has to be done in two steps, so eval will help: eval echo \${$b[*]}. (Note the \ which ensures that the first $ will pass the first step, the variable expansion, and will be only expanded in the second step by eval.)

According to Parameter Expansion ! is both used for indirect expansion ({!a}), Names matching prefix (${!a*}) and List of array keys (${!a[*]}). Because List of array keys has the same syntax as your intended indirect expansion+array element expansion, the later is not supported as is.

3
  • 2
    ${!a} does expand to the value of the variable whose name is $a. This is rather tersely described in the manual, in the parragraph that begins with “If the first character of parameter is an exclamation point (!), a level of variable indirection is introduced.” Commented Sep 6, 2011 at 7:35
  • Yep - @Gilles is right, but @manatwork, on second reading, I noticed that ${! is kinda ambigious since if it's an array you're dealing with, the behaviour is different.
    – Eric Smith
    Commented Sep 6, 2011 at 8:50
  • @Gilles you are right on that sentence, but sadly it not applies as "The exceptions to this are the expansions of ${!prefix*} and ${!name[@]} described below." But my reply is certainly an ambiguous mess, so I will edit it.
    – manatwork
    Commented Sep 6, 2011 at 9:02
0

To access arrays indirectly, just add [@] to the indirect variable b=a[@].

If this variables are set:

a=(one two three)
printf '<%s> ' "${a[@]}"; echo

Then, this will work:

b="a[@]"
printf '<%s> ' "${!b}"

Or simply:

echo "${!b}"

Such array could be copied as this:

newArr=( "${!b}" )

And then printed with:

declare -p newArr

In one script:

#!/bin/bash
a=(one two three)
echo "original array"
printf '<%s> ' "${a[@]}"; echo

echo "Indirect array with variable b=a[@]"
b="a[@]"
printf '<%s> ' "${!b}"; echo

echo "New array copied in newArr, printed with declare"
newArr=( "${!b}" )
declare -p newArr

Of course, all the above will copy a non-sparse array. One in which all indexes have a value.

sparse arrays

An sparse array is one which may have non-defined elements.
For example a[8]=1234 defines one element, and, in bash, 0 to 7 do not exist.

To copy such sparse array use this method

  1. Print the old array:

    $ oldarr[8]=1234
    $ declare -p oldarr
    declare -a oldarr=([8]="1234")
    
  2. Replace the name of the array and capture the string:

    $ str=$(declare -p oldarr | sed 's/oldarr=/newarr=/')
    
  3. Eval the string so created, the new array has been defined:

    $ eval "$str"
    $ declare -p newarr
    declare -a newarr=([8]="1234")
    

You must log in to answer this question.

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