Bash Scripting - Iterating Through "Variable" Variable Names for a List of Associative Arrays

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 the declare or local 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, running

declare -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 to ref 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 the unset builtin (see Bourne Shell
Builtins).
Otherwise, if unset 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 the IFS 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



Leave a reply



Submit