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

重要な要約:バックグラウンドで実行される機能は別々のプロセスです。バックグラウンドで実行されている間、子はデータのコピーを受け取ります。このコピーに対する変更は親コピーに伝播されません。


コードに行番号を追加してみましょう。

     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は自分自身を2つのプロセスに分岐します。 1つは、実行され続けて10行目で停止される親プロセスです。waitこのプロセスは、9行前に存在していたデータのコピーを使用してfoo関数の内容を実行します。

観察:親プロセスは関数のコードを実行しませんfooが、子プロセスは行2と3のみを実行します。

子の行2は、他のリンクされた回答で説明されているように配列参照照会を実行します。fooarray["mainkey"]上記のコピー操作のため、子の値は「mainvalue」です。次に、3行目に「fookey」を追加しますが、これを行います。コピー材料。 3行目以降は、3行目の割り当てが成功したため、子プロセスは正常に終了します。

親は実行されず、foo子はコピーを変更するため、親は"fookey"変更を見ることができません。

子アイテムが親アイテムを変更できるようにするには、継続的なプロセス間通信メカニズム(ファイルやパイプなどの「IPC」)を使用し、そのIPCを読み取って更新して、親アイテムがデータを「選択」するようにする必要があります。コピー。

答え2

最もエレガントではなく、質問に正確に答えることはありませんが(これは配列を内部で変更しません)、通常の戻り値(ish)のようにバックグラウンド関数で修正された配列を出力できます。出力をキャプチャする代わりにプロセスを使用し、更新を直接コピーします。

 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

このプロセスリダイレクト方法を引き続き使用しながら、この状況を処理するには、次の2つのアプローチを考えてみます。

  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

関連情報