Indirect return of all elements in an array

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.

Answers:

Thank you for visiting the Q&A section on Magenaut. Please note that all the answers may not help you solve the issue immediately. So please treat them as advisements. If you found the post helpful (or not), leave a comment & I’ll get back to you as soon as possible.

Method 1

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

Method 2

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.)

Method 3

${!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.

Method 4

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")


All methods was sourced from stackoverflow.com or stackexchange.com, is licensed under cc by-sa 2.5, cc by-sa 3.0 and cc by-sa 4.0

0 0 votes
Article Rating
Subscribe
Notify of
guest

0 Comments
Oldest
Newest Most Voted
Inline Feedbacks
View all comments
0
Would love your thoughts, please comment.x
()
x