1

I have a lot of associative arrays and i want to use only 1 loop. select the array for loop by a given name

I want to select/build a part of the arrayname with a variable and than loop with that name, but it doesn't work.

Like in OUTPUT3 and OUTPUT4, but the syntax is wrong.

For output 3 i receive: "bash wrong substitution"

For output 4 i receive: "only the arrayname and 0"

#!/bin/bash
clear

declare -A a1 a2 a3

a1['1']="1-1V"

a2['1']="2-1V"
a2['2']="2-2V"

a3['1']="3-1V"
a3['2']="3-2V"
a3['3']="3-3V"

# 1 OUTPUT WORKS
for i in ${!a1[*]}
do
echo -e "$i : ${a1[$i]}"
done

# 2 OUTPUT WORKS
for i in ${!a2[*]}
do
echo -e "$i : ${a2[$i]}"
done

# 3 OUTPUT - WRONG SYNTAX
selectkey="3"
for i in ${!a$selectkey[@]}
do
echo -e "$i : ${a$selectkey[$i]}"
done

# 4 OUTPUT - WRONG SYNTAX
key="3"
aselect="a${key}[*]"
# THIS ECHO WORKS
echo -e "ARRAY: ${!aselect}"

for i in ${!aselect[@]}
do
echo -e "$i : ${aselect[$i]}"
done

UPDATE

The solution is nameref | declare -n

This works for me now:

#!/bin/bash
clear

declare -A a1 a2 a3

a1['1']="1-1V"

a2['1']="2-1V"
a2['2']="2-2V"

a3['1']="3-1V"
a3['2']="3-2V"
a3['3']="3-3V"

varname="a3"
counter=1

declare -n refname=${varname}

for i in "${!refname[@]}"
do
echo -e "$counter ${refname[$counter]}"
counter=$((counter+1))
done
4
  • Please don't use CAPS for variable names in shell scripts. That is just asking for trouble.
    – terdon
    Jan 17, 2023 at 11:59
  • ok i will edit the question
    – Z0OM
    Jan 17, 2023 at 12:00
  • 2
    Oh no, don't edit for that! I was just giving some general advice. More importantly, please add some context here. You are just dumping some code with "it doesn't work" but you don't explain how it fails, how you use it, or what you really need to do. Are you looking for namerefs?
    – terdon
    Jan 17, 2023 at 12:01
  • @terdon namerefs was the right idea thanks, i got it with declare -n
    – Z0OM
    Jan 17, 2023 at 14:23

3 Answers 3

5

Use a "nameref": declare -n a=b makes variable a an alias for variable b.

# 3 OUTPUT - use a "nameref"
selectkey="3"
declare -n ary="a$selectkey"

for i in "${!ary[@]}"
do
    echo "$i : ${ary[$i]}"
done

outputs

3 : 3-3V
2 : 3-2V
1 : 3-1V

Associative arrays are inherently unordered.

For #4, you are using "indirect expansion" as described in Shell Parameter Expansion, but you can't get to the array's indices with that technique.

0
0

bash does not have multidimensional arrays. However, you can implement them with an associative array and key concatenation (key1 and key2 represented as the combined string key1,key2). Here are some examples.

Given your starting scenario,

declare -A a

a[1,1]='1-1V'
a[2,1]='2-1V' a[2,2]='2-2V'
a[3,1]='3-1V' a[3,2]='3-2V' a[3,3]='3-3V'

All a[1,*] values:

for i in $(printf "%s\n" "${!a[@]}" | awk -F, -vk='1' '$1==k {print $2}')
do
    printf "%s: %s\n" "$i" "${a[1,$i]}"
done

Output

1: 1-1V

All a[*,2] values:

for i in $(printf "%s\n" "${!a[@]}" | awk -F, -vk='2' '$2==k {print $1}')
do
    printf "%s: %s\n" "$i" "${a[$i,2]}"
done

Output

3: 3-2V
2: 2-2V

An alternative way of producing the set of keys:

for i in $(printf "%s\n" "${!a[@]}" | awk -F, -vk='1' '$2==k')
do
    printf "%s: %s\n" "$i" "${a[$i]}";
done

Output

1,1: 1-1V
3,1: 3-1V
2,1: 2-1V

Notice that in all cases the keys are not in numeric order. If this is required you could modify the printf | awk construct to pipe through sort -n (assuming numeric keys).

You can also generalise the awk construct so that you can specify the field number as well as the required key value. This would help with encapsulation, perhaps by hiding the key generation in a function

awk -F, -vfield=2 -vkey=1 '$field==key'
0

If your indexes are numbers, you're likely better off using regular arrays rather than associative arrays. Both can be sparse, but regular arrays let you iterate over the keys in numerical order, while for associative arrays it's essentially random. (In other languages, you might need associative arrays to get a sparse array.) Compare:

$ declare -A a=([11]=a [22]=b [33]=c ) 
$ echo "${a[@]}"
b a c
$ declare -a b=([11]=a [22]=b [33]=c)
$ echo "${b[@]}"
a b c

Then again, if your indexes are numbers and the arrays are not sparse, you can use associative arrays to fake a two-dimensional array, and iterate over the indexes with a manual loop.

declare -A a
a[1,1]="1-1V"

a[2,1]="2-1V"
a[2,2]="2-2V"

a[3,1]="3-1V"
a[3,2]="3-2V"
a[3,3]="3-3V"

x=3
i=1
while [[ ${a[$x,$i]+set} == set ]]; do
    echo "${a[$x,$i]}"
    ((i++))
done

(Of course you could emulate a sparse array here by setting the "nonexisting" elements to some special value which you'd then ignore inside the loop.)

Ksh also actually supports two-dimensional arrays, or, well, at least nested lists:

a[1][1]="1-1V"

a[2][1]="2-1V"
a[2][2]="2-2V"

a[3][1]="3-1V"
a[3][2]="3-2V"
a[3][3]="3-3V"

x=3
for x in "${a[x][@]}"; do
    echo "$x"
done

though this doesn't work:

for x in "${a[@][1]}"; do
    echo "$x"
done

Though, needing complex data structures is also one of the situations where it might be useful to consider switching from the shells to some other programming language.

In any case, don't use ${!array[*]}, or ${!array[@]} (with or without the !), but "${!array[@]}" instead, as that's the only one that keeps values containing whitespace intact. Try e.g. z=("foo bar" "zoom") and then compare for x in ${z[*]}; do echo $x; done with the same using "${z[*]}" or "${z[@]}".

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .