3

I am having a bit of trouble while looping through multiple associative arrays in bash.

Here is the code I'm running: (stripped of the actual information)

arrays=("arrayone" "arraytwo")
declare -A arrayone=(["one"]=1 ["two"]=2)
declare -A arraytwo=(["text with spaces"]=value ["more text with spaces"]=differentvalue)
for array in ${arrays[*]} 
 do 
 for key in $(eval echo $\{'!'$array[@]\})   
       do 
       echo "$key"
       done
 done

This works perfectly fine until I run into a key value that has spaces in it. No matter what I do, I cannot get items with spaces to be treated correctly.

I would appreciate any ideas you have on how to get this working. If there is, a better way to do this, I'd be happy to hear it. I don't mind scratching this and starting over. It's just the best I've been able to come up with so far.

Thanks!

5
  • Quoted expansion of [@] is needed to preserve spaces. Commented Mar 16, 2015 at 18:43
  • I'm aware of that, but I can't figure out how to do it in this particular instance. (It messes with my eval command) I've tried various combinations of double and single-quotes with backslashes, etc. and googled this to death already. Two hours later, I'm here. Commented Mar 16, 2015 at 18:58
  • Hm... true, $() is going to throw that away even if you get the internal expansion right. I don't think you can do it this way. You probably need to write a function for the inner loop and pass it $array[@] and do the indirect expansion in the function. Like isSubset from here. Commented Mar 16, 2015 at 19:07
  • Thanks, I think I can get somewhere with that. Commented Mar 16, 2015 at 19:11
  • Typically, to iterate over the values of an array where the name is dynamic, you'd need a placeholder var for the indirect expansion: tmp="${array}[@]"; for elem in "${!tmp}"; ... -- however, using that idiom, you can't iterate over the keys: this does not work: tmp='!'"${array}[@]"; for key in "${!tmp}"; ... -- this ${!tmp} expands to nothing. You may be reduced to parsing the output of declare -p "$array" Commented Mar 16, 2015 at 20:12

1 Answer 1

4

Bash added the nameref attribute in 4.3. It allows you to make a name specifically a reference to another. In your case, you would do

declare -A assoc_one=(["one"]=1 ["two"]=2)
declare -A assoc_two=(["text with spaces"]=value ["more text with spaces"]=differentvalue)
declare -n array # Make it a nameref
for array in "${!assoc_@}"; do 
    for key in "${!array[@]}"; do
        echo "'$key'"
    done
done

and you get

'one'
'two'
'text with spaces'
'more text with spaces'

Names were changed to protect the idioms. I mean, I changed the array names so I could do "${!assoc_@}" without making array a special case.

Sign up to request clarification or add additional context in comments.

4 Comments

"Names were changed to protect the idioms" was worth an up-vote in its own right. Good to know that you can defer binding a nameref, and even rebind iteratively when using a declare -n variable as a for control variable.
@mklement0 what's more interesting is that I can't seem to rebind manually inside a while loop. This could lead to a followup SO question if I have time to do some research.
Yes, the for behavior is special (mentioned in the man page). To do it in a while loop, move the declare -n statement inside the loop; note that you must bind as part of the declare -n statement for this to work; e.g., while ...; do declare -n nameref=...; done.
Thanks! I knew there had to be a way to do this, but I wasn't getting anywhere. This was what I needed.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.