Reputation: 12214
How can I make this code work?
#!/bin/bash
ARRAYNAME='FRUITS'
FRUITS=( APPLE BANANA ORANGE )
for FRUIT in ${!ARRAYNAME[@]}
do
echo ${FRUIT}
done
This code:
echo ${!ARRAYNAME[0]}
Prints APPLE. I'm tryng to do something similar but with "[@]" to iterate over the array.
Thanks in advance,
Upvotes: 44
Views: 18728
Reputation: 79
I think the proper way and the best answer to his question has to do with an actual indirect reference, makes the least modifications to the asker's original code, and you can even do this with an associative array.
Minimally modified code for the OP
declare -n ARRAYNAME='FRUITS'
declare -a FRUITS=( APPLE BANANA ORANGE )
for FRUIT in ${!ARRAYNAME[@]}
do
echo "${ARRAYNAME[${FRUIT}]}"
done
Output
APPLE
BANANA
ORANGE
Usage in an associative array
declare -A associative_array
declare -n array_name=associative_array
associative_array[kittens]='cat'
associative_array[puppies]='dog'
associative_array[kitties]='cat'
associative_array[doggies]='dog'
for name in ${!array_name[@]} ; do
echo $name has the value of "${associative_array[$name]}"
done
Output:
puppies has the value of dog
kittens has the value of cat
kitties has the value of cat
doggies has the value of dog
Rather than having to read the entire man page for bash, simply using the builtin help
(
$ help help
help: help [-dms] [pattern ...]
Display information about builtin commands.
Displays brief summaries of builtin commands. If PATTERN is
specified, gives detailed help on all commands matching PATTERN,
otherwise the list of help topics is printed.
Options:
-d output short description for each topic
-m display usage in pseudo-manpage format
-s output only a short usage synopsis for each topic matching
PATTERN
Arguments:
PATTERN Pattern specifying a help topic
Exit Status:
Returns success unless PATTERN is not found or an invalid option is given.
)
Declare's usage:
declare: declare [-aAfFgilnrtux] [-p] [name[=value] ...]
Set variable values and attributes.
Declare variables and give them attributes. If no NAMEs are given,
display the attributes and values of all variables.
Options:
-f restrict action or display to function names and definitions
-F restrict display to function names only (plus line number and
source file when debugging)
-g create global variables when used in a shell function; otherwise
ignored
-p display the attributes and value of each NAME
Options which set attributes:
-a to make NAMEs indexed arrays (if supported)
-A to make NAMEs associative arrays (if supported)
-i to make NAMEs have the `integer' attribute
-l to convert the value of each NAME to lower case on assignment
-n make NAME a reference to the variable named by its value
-r to make NAMEs readonly
-t to make NAMEs have the `trace' attribute
-u to convert the value of each NAME to upper case on assignment
-x to make NAMEs export
Using `+' instead of `-' turns off the given attribute.
Variables with the integer attribute have arithmetic evaluation (see
the `let' command) performed when the variable is assigned a value.
When used in a function, `declare' makes NAMEs local, as with the `local'
command. The `-g' option suppresses this behavior.
Exit Status:
Returns success unless an invalid option is supplied or a variable
assignment error occurs.
Upvotes: 1
Reputation: 2052
This answer comes very late, but I guess there is much cleaner approach than those presented up to this moment (with all the respects to their authors).
It is about using -n
option of declare
/local
bash built-in. (For more info type help declare
in your bash).
So here we go:
ARRAYNAME='FRUITS';
FRUITS=(APPLE BANANA ORANGE);
# This is the critical addition. With help of option `-n` we declare
# variable `fruits` as indirect reference to another variable. Anytime
# we refer to ${fruits} we would actually refer to a variable whose
# name is stored in `fruits` variable:
declare -n fruits="${ARRAYNAME}";
# Here we use ${fruits} as ordinary variable, but in reality it refers
# to `FRUITS` variable:
for FRUIT in ${fruits[@]}; do
echo "${FRUIT}";
done;
And the result is:
APPLE
BANANA
ORANGE
Upvotes: 8
Reputation: 2292
eval
executes code containing array elements, even if they contain, for example, command substitutions. It also changes the array elements by interpreting bash metacharacters in them.
A tool that avoids these problems is the declare
reference, see man bash
under declare:
-n Give each name the nameref attribute, making it a name reference to another variable. That other variable is defined by the value of name. All references, assignments, and attribute modifications to name, except those using or changing the -n attribute itself, are performed on the variable referenced by name's value. The nameref attribute cannot be applied to array variables.
#!/bin/bash
declare -n ARRAYNAME='FRUITS'
FRUITS=(APPLE BANANA ORANGE "BITTER LEMON")
for FRUIT in "${ARRAYNAME[@]}"
do
echo "${FRUIT}"
done
Upvotes: 14
Reputation: 2474
Here's a way to do it without eval.
See Bash trick #2 described here: http://mywiki.wooledge.org/BashFAQ/006
Seems to work in bash 3 and up.
#!/bin/bash
ARRAYNAME='FRUITS'
tmp=$ARRAYNAME[@]
FRUITS=( APPLE BANANA ORANGE "STAR FRUIT" )
for FRUIT in "${!tmp}"
do
echo "${FRUIT}"
done
Here's a more realistic example showing how to pass an array by reference to a function:
pretty_print_array () {
local arrayname=$1
local tmp=$arrayname[@]
local array=( "${!tmp}" )
local FS=', ' # Field seperator
local var
# Print each element enclosed in quotes and separated by $FS
printf -v var "\"%s\"$FS" "${array[@]}"
# Chop trailing $FS
var=${var%$FS}
echo "$arrayname=($var)"
}
FRUITS=( APPLE BANANA ORANGE "STAR FRUIT" )
pretty_print_array FRUITS
# prints FRUITS=("APPLE", "BANANA", "ORANGE", "STAR FRUIT")
Upvotes: 21
Reputation:
Despite the simple OP question, these answers won't scale for the most common, real use-cases, i.e., array elements containing whitespace or wildcards that should not yet be expanded to filenames.
FRUITS=( APPLE BANANA ORANGE 'not broken' '*.h')
ARRAYNAME=FRUITS
eval ARRAY=\(\${$ARRAYNAME[@]}\)
$ echo "${ARRAY[4]}"
broken
$ echo "${ARRAY[5]}"
config.h
$
This works:
FRUITS=( APPLE BANANA ORANGE 'not broken' '*.h')
ARRAYNAME=FRUITS
eval ARRAY="(\"\${$ARRAYNAME[@]}\")"
$ echo "${ARRAY[3]}"
not broken
$ echo "${ARRAY[4]}"
*.h
$
Just as you should get in the habit of using "$@"
not $@
, always quote inside ( )
for array expansions, unless you want filename expansion or know there's no possibility of array elements containing whitespace.
Do this: X=("${Y[@]}")
Not this: X=(${Y[@]})
Upvotes: 1
Reputation: 3908
I just wanted to add another useful use-case. I was searching the web for a solution to a different, but related problem
ARRAYNAME=( FRUITS VEG )
FRUITS=( APPLE BANANA ORANGE )
VEG=( CARROT CELERY CUCUMBER )
for I in "${ARRAYNAME[@]}"
do
array="${I}[@]"
for fruit in "${!array}"; do
echo $fruit
done
done
Upvotes: -1
Reputation: 28029
${!ARRAYNAME[@]}
means "the indices of ARRAYNAME
". As stated in the bash man page since ARRAYNAME
is set, but as a string, not an array, it returns 0
.
Here's a solution using eval
.
#!/usr/bin/env bash
ARRAYNAME='FRUITS'
FRUITS=( APPLE BANANA ORANGE )
eval array=\( \${${ARRAYNAME}[@]} \)
for fruit in "${array[@]}"; do
echo ${fruit}
done
What you were originally trying to do was create an Indirect Reference. These were introduced in bash version 2 and were meant to largely replace the need for eval
when trying to achieve reflection-like behavior in the shell.
What you have to do when using indirect references with arrays is include the [@]
in your guess at the variable name:
#!/usr/bin/env bash
ARRAYNAME='FRUITS'
FRUITS=( APPLE BANANA ORANGE )
array="${ARRAYNAME}[@]"
for fruit in "${!array}"; do
echo $fruit
done
All that said, it's one thing to use Indirect References in this trivial example, but, as indicated in the link provided by Dennis Williamson, you should be hesitant to use them in real-world scripts. They are all but guaranteed to make your code more confusing than necessary. Usually you can get the functionality you need with an Associative Array.
Upvotes: 45