Though it may not be the most elegant, and it doesn't quite answer your question exactly (this would not modify the array in place), you could output the modified array from the background function like a normal return (ish) then use process substitution to capture the output and copy the update yourself.
1 #!/bin/bash
2
3 foo () {
4 declare -n fooarray="$1"
5 fooarray["fookey"]=foovalue
6 echo "${fooarray[@]@K}"
7 }
8
9 declare -A myarray
10 myarray["mainkey"]=mainvalue
11
12 # Execute "foo myarray" asynchronously using [process substitution][1]
13 # No need to run it in the background (I.e. using "&")
14 exec 3< <(foo myarray)
15 p=$!
16 wait $p
17
18 echo "<pre> ${myarray[*]@A}"
19
20 # Capture the return into an array
21 # Bash will apply word splitting here
22 ret=($(cat <&3))
23 echo "<ret> ${ret[*]@A}"
24
25 # Copy "ret" into "myarray"
26 # Remove auto escaping bash does when using @K
27 for (( i=1; i<${#ret[@]}; i+=2 )); do myarray[${ret[$i-1]}]=${ret[$i]//\"/}; done
28 echo "<post> ${myarray[*]@A}"
29
30 for key in "${!myarray[@]}"; do
31 printf '%s = %s\n' "$key" "${myarray[$key]}"
32 done
Output:
<pre> declare -A myarray=([mainkey]="mainvalue" )
<ret> declare -a ret=([0]="fookey" [1]="\"foovalue\"" [2]="mainkey" [3]="\"mainvalue\"")
<post> declare -A myarray=([fookey]="foovalue" [mainkey]="mainvalue" )
fookey = foovalue
mainkey = mainvalue
Reference for process substitution
Update
As mentioned in the comments, there is one caveat here: This solution assumes each value in the associative array has a single word value. E.g. if "foovalue" had been "foo value", Bash's word splitting would have caused all kinds of mayhem:
Output (if foovalue were set to "foo value" on line 5):
<pre> declare -A myarray=([mainkey]="mainvalue" )
<ret> declare -a ret=([0]="fookey" [1]="\"foo" [2]="value\"" [3]="mainkey" [4]="\"mainvalue\"")
<post> declare -A myarray=([fookey]="foo" [mainkey]="mainvalue" ["value\""]="mainkey" )
fookey = foo
mainkey = mainvalue
value" = mainkey
If you want to deal with this situation while still using this process redirection method, I can think of two approaches:
- Return the assignment form of the expansion (@A) then use eval to create it.
- Apply a more advanced parsing before the loop to properly capture the start and end of each variable in the return string.
Here is an example of (1) above:
1 #!/bin/bash
2
3 foo () {
4 declare -n fooarray="$1"
5 fooarray["fookey"]="foo value"
6 echo "${fooarray[@]@A}"
7 }
8
9 declare -A myarray
10 myarray["mainkey"]=mainvalue
11
12 # Execute "foo myarray" asynchronously using [process substitution][1]
13 # No need to run it in the background (I.e. using "&")
14 exec 3< <(foo myarray)
15 wait $!
16
17 echo "<pre> ${myarray[*]@A}"
18
19 # Capture the return into a string
20 ret="$(cat <&3)"
21 echo "<ret> ${ret[*]}"
22
23 # Note the returned array is named "myarray"
24 # If you want to overwrite "myarray" with the return entirely,
25 # you can call eval on it directly, otherwise, swap the name
26 ret="${ret[*]/myarray/fooarray}"
27 echo "<rv2> ${ret[*]}"
28
29 # Eval ret into existence
30 eval "${ret[*]}"
31
32 # Copy "fooarray" into "myarray"
33 # Quote to prevent globing or word splitting
34 for key in "${!fooarray[@]}"; do myarray[$key]="${fooarray[$key]}"; done
35 echo "<post> ${myarray[*]@A}"
36
37 for key in "${!myarray[@]}"; do
38 printf '%s = %s\n' "$key" "${myarray[$key]}"
39 done
Output:
<pre> declare -A myarray=([mainkey]="mainvalue" )
<ret> declare -A myarray=([fookey]="foo value" [mainkey]="mainvalue" )
<rv2> declare -A fooarray=([fookey]="foo value" [mainkey]="mainvalue" )
<post> declare -A myarray=([fookey]="foo value" [mainkey]="mainvalue" )
fookey = foo value
mainkey = mainvalue