Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Array of associative array names in bash

Tags:

arrays

bash

shell

I'm trying to manage a list of associative arrays with an array in bash, and I can't seem to pick up what's not going right.

What I'm trying to do:

array=(a b c d)

for i in ${array[@]}; do
    declare -A $i
done

a[key]=avalue
b[key]=bvalue
c[key]=cvalue
d[key]=dvalue

That all seems to work fine as I can manually return values by referencing ${a[key]} just fine.

However, when I'm trying to iterate through using the array variable it's not really giving me what I expect.

for index in ${array[@]}; do
  echo ${index[key]}
done

Is returning the same as if I were to run

for index in ${array[@]}; do
  echo $index
done

I feel like I'm missing something simple, but searching for an answer isn't turning up any solutions. Any assistance would be appreciated.

like image 540
Mycah Avatar asked Feb 12 '15 18:02

Mycah


1 Answers

Here's one solution, using shell indirection. This will work with any bash which supports associative arrays. The indirection must contain the entire reference, including subscripts, which is a bit awkward.

for index in "${array[@]}"; do
  indexkey=${index}[key]
  echo "${!indexkey}"
done

With a modern bash (at least v4.3), you can use nameref declarations, which create aliases. This is a lot more convenient, because you can use different keys with the same alias:

for index in "${array[@]}"; do
  declare -n subarray=$index
  echo "${subarray[key]}"
done

Something approaching an explanation.

As Etan Reisner points out in a comment, this question is dealt with at some length in a Bash FAQ entry. However, on the date that I write, that FAQ entry includes the disclaimer "Overhauling this page will take some time and work", and it is certainly true that the FAQ entry is not currently as clear as one might like. So here's my brief summary:

  1. declare (and other related builtins, including export, local, and typeset) evaluate their arguments. So you can build up a variable name in a declare statement.

  2. Since declare can also be used to assign values (as long as you use the same declaration), you can build up an index variable name in a declare statement.

  3. If you are using bash4.3 or better, you can use namerefs (typeset -n or declare -n) in order to simulate array values. That's really the only way you can return an array from a bash function, but it is still a bit awkward since it requires the caller of the function to provide an argument with the name of the array; furthermore, it is not entirely robust since the name will be used in the scope of the function so it might be shadowed by a local variable. Caution is required.

  4. There is probably no other good reason to use variable indirection. If you find yourself needing it, think about whether you could structure your program differently. You can collapse associative keys with string concatenation. For example:

    my_array[${place}:${feature}]
    

    will work find as long as no value for ${place} contains a colon (of course, you could use a different character instead of colon).

  5. Watch out with keys. If an array is declared associative, the key is evaluated more or less normally, but if it is a normal indexed array, the key is evaluated as an arithmetic expression. The consequence is that

    declare -A assoc
    declare -a indexed
    key=42
    # This assigns the element whose key is "key"
    assoc[key]=foo
    # This assigns the element whose key is 42
    indexed[key]=foo
    # If $key had not been defined, the above would have assigned
    # the element whose key was 0, without an error message
    
like image 158
rici Avatar answered Nov 08 '22 12:11

rici