![Bash 将关联数组传递给后台函数/从后台函数传递一个关联数组](https://linux22.com/image/202903/Bash%20%E5%B0%86%E5%85%B3%E8%81%94%E6%95%B0%E7%BB%84%E4%BC%A0%E9%80%92%E7%BB%99%E5%90%8E%E5%8F%B0%E5%87%BD%E6%95%B0%2F%E4%BB%8E%E5%90%8E%E5%8F%B0%E5%87%BD%E6%95%B0%E4%BC%A0%E9%80%92%E4%B8%80%E4%B8%AA%E5%85%B3%E8%81%94%E6%95%B0%E7%BB%84.png)
我试图通过引用将 bash 关联数组传递到函数中,然后能够在函数完成后在主脚本中看到更改的内容。我找到了似乎是最直接的方法这里除了我的情况之外,该函数正在运行在后台。似乎无论我做什么,我都无法让上面的链接解决方案在这种情况下工作。
在下面的代码片段中,我从上面的链接中获取了工作示例代码,并简单地在函数调用中添加了“&”,并在下一行添加了“wait”,以尽可能简单地演示该问题。
我怀疑 bash 试图阻止主脚本和后台函数互相踩踏,但我不知道如何解决。
示例代码:
foo () {
declare -n fooarray="$1"
fooarray["fookey"]=foovalue
}
declare -A myarray
myarray["mainkey"]=mainvalue
foo myarray &
wait
for key in "${!myarray[@]}"; do
printf '%s = %s\n' "$key" "${myarray[$key]}"
done
输出:
bash-4.4$ ./test.sh
mainkey = mainvalue
任何帮助,将不胜感激。我知道我可能会做一些愚蠢的事情,例如将数组的内容写入文件,然后将其解析回来,但我希望有一个比这更优雅的解决方案。
答案1
TL;DR:在后台运行的函数是一个单独的进程。在后台运行时,孩子会获得一份数据副本。对该副本所做的任何更改都不会传播到父级。
让我们在代码中添加一些行号:
1 foo () {
2 declare -n fooarray="$1"
3 fooarray["fookey"]=foovalue
4 }
5
6 declare -A myarray
7
8 myarray["mainkey"]=mainvalue
9 foo myarray &
10 wait
11
12 for key in "${!myarray[@]}"; do
13 printf '%s = %s\n' "$key" "${myarray[$key]}"
14 done
在第 9 行,bash 将自身分叉为两个进程:一个是父进程,它继续运行并在wait
第 10 行停止;另一个是父进程,它继续运行并在第 10 行停止;另一个是子进程,它使用第 9 行之前存在的数据副本来运行函数 foo 的内容。
观察:父进程从不运行 function 中的代码foo
,而子进程只运行第 2 行和第 3 行。
在子项中,第 2 行执行数组引用查找,如另一个链接答案中所述。fooarray["mainkey"]
由于提到的复制操作,子级中的值为“mainvalue”。然后,第 3 行添加了“fookey”,但正在执行此操作复印件的数据。在第 3 行之后,子进程成功退出,因为第 3 行的赋值成功。
由于父级永远不会运行foo
,并且子级修改副本,因此父级永远不会看到"fookey"
更改。
如果您希望子级修改父级,那么您必须使用持久的进程间通信机制(“IPC”,如文件或管道),并通过读取该 IPC 并更新其副本来让父级“选择加入”数据。
答案2
虽然它可能不是最优雅的,并且它不能完全回答您的问题(这不会就地修改数组),但您可以像正常 return (ish) 一样从后台函数输出修改后的数组,然后使用 process替换以捕获输出并自行复制更新。
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
输出:
<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
更新
正如评论中提到的,这里有一个警告:此解决方案假设关联数组中的每个值都有一个单词值。例如,如果“foovalue”是“foo value”,Bash 的分词就会导致各种混乱:
输出(如果第 5 行 foovalue 设置为“foo value”):
<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
如果你想在处理这种情况的同时仍然使用这种进程重定向方法,我可以想到两种方法:
- 返回扩展 (@A) 的赋值形式,然后使用 eval 创建它。
- 在循环之前应用更高级的解析,以正确捕获返回字符串中每个变量的开头和结尾。
以下是上述 (1) 的示例:
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
输出:
<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