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
    CommentedMar 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.CommentedSep 6, 2011 at 8:52
    • @Eric I think in ksh/bash you need eval at that stage. See my edit.CommentedSep 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
      CommentedOct 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.”CommentedSep 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.CommentedSep 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
      CommentedSep 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.

      Start asking to get answers

      Find the answer to your question by asking.

      Ask question

      Explore related questions

      See similar questions with these tags.