web-dev-qa-db-ja.com

スクリプトのbashバックグラウンドジョブが完了するまで待ちます

CPU使用率を最大化するため(EC2のDebian Lennyで物事を実行しています)、並列にジョブを起動する簡単なスクリプトがあります。

#!/bin/bash

for i in Apache-200901*.log; do echo "Processing $i ..."; do_something_important; done &
for i in Apache-200902*.log; do echo "Processing $i ..."; do_something_important; done &
for i in Apache-200903*.log; do echo "Processing $i ..."; do_something_important; done &
for i in Apache-200904*.log; do echo "Processing $i ..."; do_something_important; done &
...

私はこの作業ソリューションに非常に満足していますが、すべてのループが完了した後にのみ実行されるコードをさらに記述する方法を理解できませんでした。

これを制御する方法はありますか?

47
mark

そのためのbash組み込みコマンドがあります。

wait [n ...]
      Wait for each specified process and return its termination  sta‐
      tus.   Each  n  may be a process ID or a job specification; if a
      job spec is given, all processes  in  that  job’s  pipeline  are
      waited  for.  If n is not given, all currently active child pro‐
      cesses are waited for, and the return  status  is  zero.   If  n
      specifies  a  non-existent  process or job, the return status is
      127.  Otherwise, the return status is the  exit  status  of  the
      last process or job waited for.
77
eduffy

GNU Parallelを使用すると、スクリプトがさらに短くなり、おそらくより効率的になります。

parallel 'echo "Processing "{}" ..."; do_something_important {}' ::: Apache-*.log

これにより、CPUコアごとに1つのジョブが実行され、すべてのファイルが処理されるまで実行されます。

ソリューションは基本的に、実行前にジョブをグループに分割します。ここでは、4つのグループに32のジョブがあります。

Simple scheduling

代わりに、GNU Parallelは、プロセスが終了すると新しいプロセスを生成します-CPUをアクティブに保ち、時間を節約します。

GNU Parallel scheduling

詳しく知ることができ:

26
Ole Tange

私は最近これを行わなければならず、次の解決策に終わりました:

while true; do
  wait -n || {
    code="$?"
    ([[ $code = "127" ]] && exit 0 || exit "$code")
    break
  }
done;

仕組みは次のとおりです。

wait -n(潜在的に多数の)バックグラウンドジョブの1つが終了するとすぐに終了します。常にtrueと評価され、ループは次の状態になるまで続きます。

  1. 終了コード127:最後のバックグラウンドジョブが正常に終了しました。その場合、終了コードを無視し、コード0でサブシェルを終了します。
  2. バックグラウンドジョブのいずれかが失敗しました。その終了コードでサブシェルを終了します。

set -e、これにより、スクリプトが早期に終了し、失敗したバックグラウンドジョブの終了コードを通過することが保証されます。

4
Olivier Lalonde

これは私の粗解です:

function run_task {
        cmd=$1
        output=$2
        concurency=$3
        if [ -f ${output}.done ]; then
                # experiment already run
                echo "Command already run: $cmd. Found output $output"
                return
        fi
        count=`jobs -p | wc -l`
        echo "New active task #$count:  $cmd > $output"
        $cmd > $output && touch $output.done &
        stop=$(($count >= $concurency))
        while [ $stop -eq 1 ]; do
                echo "Waiting for $count worker threads..."
                sleep 1
                count=`jobs -p | wc -l`
                stop=$(($count > $concurency))
        done
}

アイデアは、「ジョブ」を使用してバックグラウンドでアクティブな子の数を確認し、この数が下がるまで待つ(子が終了する)ことです。子が存在すると、次のタスクを開始できます。

ご覧のとおり、同じ実験/コマンドを複数回実行することを避けるために、少し余分なロジックもあります。それは私のために仕事をします。しかし、このロジックはスキップするか、さらに改善することができます(たとえば、ファイル作成のタイムスタンプ、入力パラメーターなどを確認します)。

0
Radu