Bash scripting - Iterating through variable variable names for a list of associative arrays
The difficulty here stems from the fact that the syntax for indirect expansion (${!nameref}
) clashes with the syntax for extracting keys from an associative arrays (${!array[@]}
). We can have only one or the other, not both.
Wary as I am about using eval
, I cannot see a way around using it to extract the keys of an indirectly referenced associative array:
keyref="queue${count}[@]"
for key in $(eval echo '${!'$keyref'}'); do ... ; done
You can however avoid eval
and use indirect expansion when extracting a value from an array given the key. Do note that the [key]
suffix must be part of the expansion:
valref="queue${count}[$key]"
echo ${!valref}
To put this in context:
for count in {1..5} ; do
keyref="queue${count}[@]"
for key in $(eval echo '${!'$keyref'}'); do
valref="queue${count}[$key]"
echo "key = $key"
echo "value = ${!valref}"
done
done
How to iterate over associative arrays in Bash
The keys are accessed using an exclamation point: ${!array[@]}
, the values are accessed using ${array[@]}
.
You can iterate over the key/value pairs like this:
for i in "${!array[@]}"
do
echo "key : $i"
echo "value: ${array[$i]}"
done
Note the use of quotes around the variable in the for
statement (plus the use of @
instead of *
). This is necessary in case any keys include spaces.
The confusion in the other answer comes from the fact that your question includes "foo" and "bar" for both the keys and the values.
Iterating over list of arrays in bash
Bash 4.3-alpha introduced the nameref attribute, which could be used in this case:
declare -A arr1=([foo]=bar)
declare -A arr2=([foo]=baz)
arrays=(arr1 arr2)
for idx in "${arrays[@]}"; do
declare -n temp="$idx"
echo "${temp[foo]}"
done
gives the output
bar
baz
As pointed out by kojiro in his comment, storing the array names in an array to iterate over is not actually required as long as the names have a shared prefix.
arrays=(arr1 arr2)
for idx in "${arrays[@]}"; do
could be replaced by
for idx in "${!arr@}"; do
Notice that despite the exclamation mark, this has nothing to do with indirect expansion.
Relevant excerpts from the reference manual
Section "Shell Parameters":
A variable can be assigned the nameref attribute using the
-n
option to thedeclare
orlocal
builtin commands (see Bash
Builtins)
to create a nameref, or a reference to another variable. This allows
variables to be manipulated indirectly. Whenever the nameref variable
is referenced or assigned to, the operation is actually performed on
the variable specified by the nameref variable's value. A nameref is
commonly used within shell functions to refer to a variable whose name
is passed as an argument to the function. For instance, if a variable
name is passed to a shell function as its first argument, runningdeclare -n ref=$1
inside the function creates a nameref variable
ref
whose value is
the variable name passed as the first argument. References and
assignments toref
are treated as references and assignments to the
variable whose name was passed as$1
.If the control variable in a
for
loop has the nameref attribute, the
list of words can be a list of shell variables, and a name reference
will be established for each word in the list, in turn, when the loop
is executed. Array variables cannot be given the-n
attribute.
However, nameref variables can reference array variables and
subscripted array variables. Namerefs can be unset using the-n
option to theunset
builtin (see Bourne Shell
Builtins).
Otherwise, ifunset
is executed with the name of a nameref variable
as an argument, the variable referenced by the nameref variable will
be unset.
Section "Shell Parameter Expansion":
${!prefix*}
${!prefix@}
Expands to the names of variables whose names begin with
prefix
,
separated by the first character of theIFS
special variable. When
@
is used and the expansion appears within double quotes, each
variable name expands to a separate word.
Get variable name while iterating over array in bash
It sounds like you want an associative array.
# to set values over time
declare -A Array=( ) || { echo "ERROR: Need bash 4.0 or newer" >&2; exit 1; }
Array[var1]=1
Array[var2]=2
Array[var3]=3
This can also be assigned at once:
# or as just one assignment
declare -A Array=( [var1]=1 [var2]=2 [var3]=3 )
Either way, one can iterate over the keys with "${!Array[@]}"
, and retrieve the value for a key with ${Array[key]}
:
for var in "${!Array[@]}"; do
val="${Array[$var]}"
echo "$var -> $val"
done
...will, after either of the assignments up top, properly emit:
var1 -> 1
var2 -> 2
var3 -> 3
How to create an associate array with value of a list of items in bash
Edit: take comments into account and replace now useless arrays by scalar strings.
As you want to set bash variables in the command's context we cannot execute them with "$cmd"
, this would not work for variable assignments. The following uses eval
, which is extremely risky, especially if you do not fully control the inputs. A better solution, but more complicated, would be to use other variables for the execution environment, declare functions to limit the scope of variables and/or restore them afterwards, use eval
only in last resort and only after sanitizing its parameters (printf '%q'
)... Anyway, you have been warned.
Storing bash commands and their arguments in variables is not recommended. But if you really need this it would be better to store the command names and the full commands in 2 different variables. They could be associative arrays or, if your bash is recent enough and supports namerefs, scalar variables named from your keys (if they are valid bash variable names).
Example where the key is stored in bash variable k
, and the command is the second of your own example, plus some dummy arguments:
k="e"
# add a new command with key "$k"
declare -n cmd="${k}_cmd" lcmd="${k}_lcmd"
cmd="dolphin"
lcmd="XDG_CURRENT_DESKTOP=KDE dolphin arg1 arg2 'arg 3'"
...
# launch command with key "$k"
declare -n cmd="${k}_cmd" lcmd="${k}_lcmd"
if not_running "$cmd"; then
eval "$lcmd"
fi
Demo with key foo
, command printf
and arguments '%s\n' 'a b' 'c d'
:
$ k="foo"
$ declare -n cmd="${k}_cmd" lcmd="${k}_lcmd"
$ cmd="printf"
$ lcmd="printf '%s\n' 'a b' 'c d'"
$ eval "$lcmd"
a b
c d
Using a variable for associative array key in Bash
You may want to use "NUM" as the name of the associative array and file name as the key. Then you can rewrite your code as:
NUM[${NAME}_AA]=$(wc -l < "$NAME")
Then rephrase your loop as:
for NAME in "${NAMES[@]}"
do
echo "max: ${NUM[${NAME}_AA]}"
done
Check your script at shellcheck.net
As an aside: all uppercase is not a good practice for naming normal shell variables. You may want to take a look at:
- Correct Bash and shell script variable capitalization
Iterate over associative array and send a single mail over the contents
Why can't just pipe the output of the for
loop to the mail
command. It could be very well possible that the command can read over from standard input via pipe. So all you need to do is
for entry in "${!AArr[@]}"; do
printf "NUMBER COUNT ON %s is : %d\n" "$entry" "${AArr[$entry]}"
done | mail
Related Topics
Find Value from One CSV in Another One (Like Vlookup) in Bash (Linux)
How to Pass Command Line Parameters with Quotes Stored in Single Variable
Docker Container Accessible Only via Cloudflare Cdn (Selected Ip Ranges)
Generate Public Ssh Key from Private Key
Errors While Installing Opencv and Ffmpeg Through Rightscripts
Having Trouble Finding the Method _Kernel_Vsyscall Within the Linux Kernel
Running Multiple Compass-Sass Watch Operations Automatically
How to Import Environment Settings into My Perl Program
Pkill Returns 255 in Combination with Another Command via Remote Ssh
Difference Between Linux Kernel and Unix Kernel(Such as Freebsd) from Programmer's Point of View
Ignoring Comma in Field of CSV File with Awk
How Can a Process Try to Access Other Process's Memory in Linux Virtual Memory System
What Happened to Socket If Network Has Broken Down
Bluetooth Error: Native Library Bluecove_Arm Not Available