3

I have an array

declare -a arr0=("'1 2 3'" "'4 5 6'")

and a variable

x=0

Then I create the new variable with the array's name

tmp="arr$x"

and I'd like to be able to expand arr0 content from this tmp variable like this

newArr=( "${!tmp}" )

and to use newArr like the ordinary array, e.g. use indices etc.


But when I try print it now, it looks like this:

$ echo ${newArr[@]}
'1 2 3'

Only the first element is stored and I don't know, how to fix it.

I've also tried to create newArr like this

newArr=( "${!tmp[@]}" )

but then it's even worse - only 0 is printed.

$ echo ${newArr[@]}
0

So, do you know, how to use an array if its name is stored in some other variable?

4
  • 1
    What are you actually trying to do? While this is probably possible, it will be ugly, hard to do, hell to debug. What is the problem that using this sort of abstract naming is attempting to solve? Maybe you should be asking about that instead. – terdon Mar 8 '17 at 11:58
  • 1
    @terdon I want to use that to use multiple arrays as one 2D array. – Eenoku Mar 8 '17 at 11:59
  • 2
    @Eenoku Rethink your choice of language for this. – Kusalananda Mar 8 '17 at 12:01
  • To copy the array just use tmp="arr$x[@]", the rest will work as is. – ImHere Mar 9 '17 at 1:49
7

It is possible with eval:

$ declare -a array=( 1 2 3 4 )
$ echo "${array[@]}"
1 2 3 4
$ p=ay
$ tmp=arr$p
$ echo "$tmp"
array
$ echo "\${${tmp}[@]}"
${array[@]}
$ echo "newarray=(\"\${${tmp}[@]}\")"
newarray=("${array[@]}")
$ eval "newarray=(\"\${${tmp}[@]}\")"
$ echo "${newarray[@]}"
1 2 3 4
$

Commands starting with echo are for illustration, eval is dangerous.

Note that the above doesn't preserve array indices for sparse arrays.

2

bash 4.3 added support for ksh93-like namerefs.

So in bash-4.3 or above you can do:

a0[5]=whatever
x=0
typeset -n var="a$x"
printf '%s\n' "${var[5]}"

But note that's a reference (a pointer, not a copy) to the variable name, not the variable (the difference matters when you have several variables by the same name in different contexts like for local variables in functions).

bash copied ksh arrays with their awkward design. Making a copy of an array in bash is difficult, you can use a helper function like:

copy_array() { # Args: <src_array_name> <dst_array_name>
  eval '
    local i
    '"$2"'=()
    for i in "${!'"$1"'[@]}"; do
      '"$2"'[$i]=${'"$1"'[$i]}
    done'
}

To be used for instance as:

$ a0[5]=123
$ x=0
$ copy_array "a$x" var
$ typeset -p var
declare -a var=([5]="123")

ksh (and bash which copied ksh) is the only shell where arrays are sparse (or are associative arrays whose keys are limited to positive integers) (also the only arrays with indices starting at 0 instead of 1, or where $array unintuitively doesn't expand to the elements but the element of indice 0). It's a lot easier with other shells.

  • rc: array_copy = $array
  • fish: set array_copy = $array
  • csh: set array_copy = ($array:q)
  • zsh or yash: array_copy=("${array[@]}"}

For indirect copy (where $var contains the source array name):

  • rc: eval array_copy = '$'$var
  • fish: eval set array_copy \$$var
  • csh: eval "set array_copy = (\$${var}:q)"
  • zsh: array_copy=("${(@P)var}")
  • yash (or zsh): eval 'array_copy=("${'"$var"'[@]}")'
7
  • To copy an (indirect) array is very easy in bash. Make the vars declare -a arr0=("'1 2 3'" "'4 5 6'"); x=0, then just use tmp="arr${x}[@]" and newArr=( "${!tmp}" ). That's it newArr is a full copy of arr0. However confusing such syntax may seem. – ImHere Mar 9 '17 at 1:56
  • @sorontar, thanks, but that would only work for non-sparse arrays, not my a0 array defined as a0[5]=123 for instance, as the copy would have newArr[0]=123 instead. – Stéphane Chazelas Mar 9 '17 at 8:08
  • Yes, and non-sparse arrays is what all other shells copy. Even if only one element (the fifth in your example) is defined, all other array elements are created. The zsh syntax will work for bash if the array is not sparse, so, at least, syntax is similar. And non-sparse arrays is by far the most common use of arrays. – ImHere Mar 9 '17 at 22:00
  • For example, if in zsh you define a[5]=23, then, that element has been created: echo ${a[5]+yes} as well as all the previous ones, for example, echo ${a[4]+yes} are yes; but echo ${a[6]+yes} is not. – ImHere Mar 9 '17 at 22:11
  • @sorontar, yes zsh arrays (like fish/rc/csh/yash and most other languages like C/perl...) are normal arrays. If you assign something to a[5], you implicitly create an array of size 5 (if its size was originally smaller). See also Test for array support by shell – Stéphane Chazelas Mar 9 '17 at 22:20
1

Indirect expansion has some exceptions, and using ! in arrays is one of the exceptions.

From man bash:

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. ${!prefix*} Names matching prefix. Expands to the names of variables whose names begin with prefix, separated by the first character of the IFS special variable.

As described in BASH FAQ06 , one workaround is like this:

arrA=("AA" "2" "4")
echo -e "array arrA contains: \c" && declare -p arrA
ref=arrA;
tmp=${ref}[@] #this can be adjusted to [1] , [2] etc to refer to particular array items
echo "Indirect Expansion Printing: ${!tmp}"

#Output
array arrA contains: declare -a arrA='([0]="AA" [1]="2" [2]="4")'
Indirect Expansion Printing: AA 2 4
1
  • If ref=arrA[@], then tmp=(${!ref}) will copy the full array values (not indexes). – ImHere Mar 9 '17 at 2:48

Not the answer you're looking for? Browse other questions tagged or ask your own question.