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.
4 Answers 4
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
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.)
-
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– Eric Smith2011年09月06日 08:52:48 +00:00Commented Sep 6, 2011 at 8:52
-
@Eric I think in ksh/bash you need eval at that stage. See my edit.Gilles 'SO- stop being evil'– Gilles 'SO- stop being evil'2011年09月06日 10:21:04 +00:00Commented 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– krb6862016年10月30日 23:45:17 +00:00Commented Oct 30, 2016 at 23:45
${!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.
-
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."Gilles 'SO- stop being evil'– Gilles 'SO- stop being evil'2011年09月06日 07:35:03 +00:00Commented 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– Eric Smith2011年09月06日 08:50:02 +00:00Commented 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– manatwork2011年09月06日 09:02:14 +00:00Commented Sep 6, 2011 at 9:02
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
Print the old array:
$ oldarr[8]=1234 $ declare -p oldarr declare -a oldarr=([8]="1234")
Replace the name of the array and capture the string:
$ str=$(declare -p oldarr | sed 's/oldarr=/newarr=/')
Eval the string so created, the new array has been defined:
$ eval "$str" $ declare -p newarr declare -a newarr=([8]="1234")
[@]
to the pointer_LIST="LIST_${whichone}[@]"
and then, useLIST=("${!_LIST}")
to copy the array. It is a good idea to use lower case variable names to avoid conflicts with environment variables (All caps).