並列プロセス:bashスクリプトの配列に出力を追加する

並列プロセス:bashスクリプトの配列に出力を追加する

task関数が呼び出されるforループがあります。この関数を呼び出すたびに、配列に追加された文字列が返されます。私はこのforループを並列化したいと思います。試してみましたが&うまくいかないようです。

これは比較できないコードです。

task (){ sleep 1;echo "hello $1"; }
arr=()

for i in {1..3}; do
    arr+=("$(task $i)")
done

for i in "${arr[@]}"; do
    echo "$i x";
done

出力は次のとおりです

hello 1 x
hello 2 x
hello 3 x

すごい!しかし、今はそれを組み合わせようとしたとき

[...]
for i in {1..3}; do
    arr+=("$(task $i)")&
done
wait
[...]

出力が空です。

アップデート#1

機能情報task:

  • この関数はtask実行に少し時間がかかり、文字列を出力します。すべての文字列が収集された後、別のforループは文字列を繰り返して他の操作を実行します。
  • 順序は重要ではありません。出力文字列は、スペースで区切られた複数の単語を含む単一行文字列で構成できます。

答え1

バックグラウンドプロセスはシェルの分岐であり、変数の変更はデフォルトのシェルには表示されないため、割り当てをバックグラウンドに送信することはできません。

ただし、複数のジョブを並列に実行し、すべてパイプに出力し、出力される内容を読み取ることができます。または実際に使用してください。プロセスの交換サブシェルのパイプでコマンドを実行したときに発生する問題を回避するには(参照)私の変数が1つの「読み込み中」ループではローカルですが、一見似ている他のループではローカルではないのはなぜですか?)

出力がアトミックに作成された1行の場合、混合されませんが、並べ替えられます。

$ task() { sleep 1; echo "$1"; }
$ time while read -r line; do arr+=("$line"); done < <(for x in 1 2 3 ; do task "$x" & done)
real    0m1.006s
$ declare -p arr
declare -a arr=([0]="2" [1]="1" [2]="3")

上記のタスクはすべてのタスクを同時に実行します。しかもGNUパラレル-PGNU xargsでは)は並行してジョブを実行するように設計されており、同時にいくつかのジョブのみを実行します。また、並列処理はジョブ出力をバッファリングするため、ジョブが行を断片化しても混合データを取得できません。

$ mapfile -t arr < <(parallel -j4 bash ./task.sh ::: {a,b,c})
$ declare -p arr
declare -a arr=([0]="a" [1]="b" [2]="c")

(Bashは上記のループmapfileと同様に、ここで入力ラインを配列として読み込みます。)while read .. arr+=()

上記のように外部スクリプトを実行するのは簡単ですが、実際にエクスポートされた関数も実行させることができます。もちろん、すべてのタスクは別々のシェルコピーで実行されるため、すべての変数などの独自のコピーがあります。

$ export -f task
$ mapfile -t arr < <(parallel task ::: {a,b,c})

上記の例は、 、ab順序を維持する場合が多いがcこれは偶然の一致です。parallel -k出力を順番に保持するには、それを使用します。

答え2

Bourneのようなシェルから移植可能なわずかに素朴で強力なアプローチです。

#!/bin/sh

task () {
    tid="$1"
    printf 'tid %d: Running...\n' "$tid"
    sleep "$(( RANDOM % 5 ))"
    printf 'tid %d: Done.\n' "$tid"
}

ntasks=10

tid=0
while [ "$tid" -ne "$ntasks" ]; do
    tid=$(( tid + 1 ))
    printf 'main: Starting task with tid=%d\n' "$tid"
    task "$tid" >"output.$tid" 2>&1 &
done

wait

tid=0
while [ "$tid" -ne "$ntasks" ]; do
    tid=$(( tid + 1 ))
    printf 'main: Processing output from task with tid=%d\n' "$tid"
    # do something with "output.$tid"
done

これは、最初のループでジョブを作成し、2番目のループで出力を処理する前にジョブが完了するのを待ちます。これは、ジョブが大量のデータを生成する場合に適しています。

実行中のジョブの数を最大4つに制限するには、初期ループを次のように変更できます。

tid=0
while [ "$tid" -ne "$ntasks" ]; do
    tid=$(( tid + 1 ))
    printf 'main: Starting task with tid=%d\n' "$tid"
    task "$tid" >"output.$tid" 2>&1 &

    if [ "$(( tid % 4 ))" -eq 0 ]; then
        wait
    fi
done

答え3

parsetあなたは(20170422以降のGNU Parallelの一部)またはenv_parset(20171222から利用可能)を探しています:

# If you have not run:
#    env_parallel --install
# and logged in again, then you can instead run this to activate (env_)parset:
. `which env_parallel.bash`

task (){
  echo "hello $1"
  sleep 1.$1
  perl -e 'print "binary\001\002\n"'
  sleep 1.$1
  echo output of parallel jobs do not mix
}
env_parset arr task ::: {1..3}
env_parset a,b,c task ::: {1..3}

echo "${arr[1]}" | xxd
echo "$b" | xxd

parsetBash/Ksh/Zsh(アレイを含む)、ash/dash(アレイを除く)をサポート。

関連情報