Bash 将关联数组传递给后台函数/从后台函数传递一个关联数组

Bash 将关联数组传递给后台函数/从后台函数传递一个关联数组

我试图通过引用将 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

如果你想在处理这种情况的同时仍然使用这种进程重定向方法,我可以想到两种方法:

  1. 返回扩展 (@A) 的赋值形式,然后使用 eval 创建它。
  2. 在循环之前应用更高级的解析,以正确捕获返回字符串中每个变量的开头和结尾。

以下是上述 (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

相关内容