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 
4
  • Please don't use CAPS for variable names in shell scripts. That is just asking for trouble.
    – terdon
    CommentedJan 17, 2023 at 11:59
  • ok i will edit the questionCommentedJan 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
    CommentedJan 17, 2023 at 12:01
  • @terdon namerefs was the right idea thanks, i got it with declare -nCommentedJan 17, 2023 at 14:23

4 Answers 4

6

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
    2

    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' 
      2

      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[@]}".

        1

        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 

          You must log in to answer this question.

          Start asking to get answers

          Find the answer to your question by asking.

          Ask question

          Explore related questions

          See similar questions with these tags.