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?

Eenoku
  • 1,125
  • 1
  • 12
  • 22
  • 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 08 '17 at 11:58
  • 1
    @terdon I want to use that to use multiple arrays as one 2D array. – Eenoku Mar 08 '17 at 11:59
  • 2
    @Eenoku Rethink your choice of language for this. – Kusalananda Mar 08 '17 at 12:01
  • To copy the array just use `tmp="arr$x[@]"`, the rest will work as is. –  Mar 09 '17 at 01:49

3 Answers3

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.

Stéphane Chazelas
  • 522,931
  • 91
  • 1,010
  • 1,501
ctx
  • 2,404
  • 10
  • 17
3

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
George Vasiliou
  • 7,803
  • 3
  • 18
  • 42
  • 1
    If `ref=arrA[@]`, then `tmp=(${!ref})` will copy the full array values (not indexes). –  Mar 09 '17 at 02:48
3

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"'[@]}")'
Stéphane Chazelas
  • 522,931
  • 91
  • 1,010
  • 1,501
  • 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. –  Mar 09 '17 at 01: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 09 '17 at 08: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. –  Mar 09 '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. –  Mar 09 '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](//unix.stackexchange.com/a/238129) – Stéphane Chazelas Mar 09 '17 at 22:20
  • An sparse array could be copied in full and with undefined values kept as they are with (no loop): `a=$(declare -p oldarr| sed 's/oldarr=/newarr=/'); eval "$a"` –  Mar 09 '17 at 23:11
  • @sorontar, true. That, as you can imagine has been discussed several times ([example](https://groups.google.com/d/msg/comp.unix.shell/qIFmEM1CVis/VPAtdPsjH_IJ)) as that's a design from the 80s. You'd need `sed '1s/.../.../'` to account for arrays with newline in their values, probably avoid `sed` (whose many implementations have a limit on the size of their pattern space) and use `${a/../..}` instead. That also means that when called in a function, it restricts the scope (-g, recently added can only declare in the global scope). OTOH, it allows copying attributes (readonly/integer...). – Stéphane Chazelas Mar 10 '17 at 07:09