670

そのスクリプトから生成されたいくつかのサブプロセスが終了するまでbashスクリプトで待機し、!=0サブプロセスのいずれかがコードで終了したときに終了コードを返す方法は!=0?

簡単なスクリプト:

#!/bin/bash
for i in `seq 0 9`; do
  doCalculations $i &
done
wait

上記のスクリプトは、生成された 10 個のサブプロセスすべてを待機しますが、常に終了ステータスを返します0(「参考文献」を参照help wait)。このスクリプトを変更して、生成されたサブプロセスの終了ステータスを検出し1、サブプロセスのいずれかがコードで終了したときに終了コードを返すようにするにはどうすればよい!=0ですか?

サブプロセスの PID を収集し、それらを順番に待ち、終了ステータスを合計するよりも良い解決策はありますか?

4

34 に答える 34

644

waitまた、(オプションで)PID待機するプロセスを$!取得PIDし、バックグラウンドで最後に起動されたコマンドを取得します。生成された各サブプロセスのを配列に格納するようにループを変更してからPID、それぞれを待機して再度ループしますPID

# run processes and store pids in array
for i in $n_procs; do
    ./procs[${i}] &
    pids[${i}]=$!
done

# wait for all pids
for pid in ${pids[*]}; do
    wait $pid
done
于 2008-12-10T14:07:51.350 に答える
328

http://jeremy.zawodny.com/blog/archives/010717.html :

#!/bin/bash

FAIL=0

echo "starting"

./sleeper 2 0 &
./sleeper 2 1 &
./sleeper 3 0 &
./sleeper 2 0 &

for job in `jobs -p`
do
echo $job
    wait $job || let "FAIL+=1"
done

echo $FAIL

if [ "$FAIL" == "0" ];
then
echo "YAY!"
else
echo "FAIL! ($FAIL)"
fi
于 2009-02-05T09:36:30.007 に答える
93

を使用した簡単な例を次に示しwaitます。

いくつかのプロセスを実行します。

$ sleep 10 &
$ sleep 10 &
$ sleep 20 &
$ sleep 20 &

次に、コマンドでそれらを待ちますwait

$ wait < <(jobs -p)

または、wait(引数なしで)すべての場合。

これは、バックグラウンドでのすべてのジョブが完了するのを待ちます。

オプションが指定されている場合-n、次のジョブが終了するのを待ち、その終了ステータスを返します。

構文についてはhelp wait、 およびを参照してください。help jobs

ただし、これは最後の ID のステータスのみを返すという欠点があるため、各サブプロセスのステータスを確認して変数に格納する必要があります。

または、失敗時にいくつかのファイルを作成するように計算関数を作成し(空または失敗ログ付き)、そのファイルが存在するかどうかを確認します。

$ sleep 20 && true || tee fail &
$ sleep 20 && false || tee fail &
$ wait < <(jobs -p)
$ test -f fail && echo Calculation failed.
于 2016-03-16T14:12:28.557 に答える
56

簡単にどうですか:

#!/bin/bash

pids=""

for i in `seq 0 9`; do
   doCalculations $i &
   pids="$pids $!"
done

wait $pids

...code continued here ...

アップデート:

複数のコメンターが指摘したように、上記はすべてのプロセスが完了するのを待ってから続行しますが、終了せず、そのうちの 1 つが失敗した場合は失敗します。@Bryan、@SamBrightman などによって提案された次の変更を行うことができます。 :

#!/bin/bash

pids=""
RESULT=0


for i in `seq 0 9`; do
   doCalculations $i &
   pids="$pids $!"
done

for pid in $pids; do
    wait $pid || let "RESULT=1"
done

if [ "$RESULT" == "1" ];
    then
       exit 1
fi

...code continued here ...
于 2014-10-07T16:05:48.917 に答える
55

GNU Parallel がインストールされている場合は、次のことができます。

# If doCalculations is a function
export -f doCalculations
seq 0 9 | parallel doCalculations {}

GNU Parallel は終了コードを提供します:

  • 0 - すべてのジョブがエラーなしで実行されました。

  • 1-253 - 一部のジョブが失敗しました。終了ステータスは、失敗したジョブの数を示します

  • 254 - 253 を超えるジョブが失敗しました。

  • 255 - その他のエラー。

詳細については、紹介ビデオをご覧ください: http://pi.dk/1

于 2012-02-16T12:16:01.183 に答える
41

これが私がこれまでに思いついたものです。子供が終了した場合に sleep コマンドを中断する方法を知りたいので、WAITALL_DELAY自分の使用法を調整する必要はありません。

waitall() { # PID...
  ## Wait for children to exit and indicate whether all exited with 0 status.
  local errors=0
  while :; do
    debug "Processes remaining: $*"
    for pid in "$@"; do
      shift
      if kill -0 "$pid" 2>/dev/null; then
        debug "$pid is still alive."
        set -- "$@" "$pid"
      elif wait "$pid"; then
        debug "$pid exited with zero exit status."
      else
        debug "$pid exited with non-zero exit status."
        ((++errors))
      fi
    done
    (("$#" > 0)) || break
    # TODO: how to interrupt this sleep when a child terminates?
    sleep ${WAITALL_DELAY:-1}
   done
  ((errors == 0))
}

debug() { echo "DEBUG: $*" >&2; }

pids=""
for t in 3 5 4; do 
  sleep "$t" &
  pids="$pids $!"
done
waitall $pids
于 2009-06-26T10:29:48.990 に答える
10

ここにリストされている良い例がたくさんありますが、私のものも入れたいと思いました。

#! /bin/bash

items="1 2 3 4 5 6"
pids=""

for item in $items; do
    sleep $item &
    pids+="$! "
done

for pid in $pids; do
    wait $pid
    if [ $? -eq 0 ]; then
        echo "SUCCESS - Job $pid exited with a status of $?"
    else
        echo "FAILED - Job $pid exited with a status of $?"
    fi
done

サーバー/サービスを並行して開始/停止し、各終了ステータスを確認するために、非常によく似たものを使用します。私にとってはうまくいきます。これが誰かを助けることを願っています!

于 2016-02-24T21:35:00.003 に答える
9

次のコードは、すべての計算が完了するまで待機し、doCalculationsのいずれかが失敗した場合は終了ステータス 1 を返します。

#!/bin/bash
for i in $(seq 0 9); do
   (doCalculations $i >&2 & wait %1; echo $?) &
done | grep -qv 0 && exit 1
于 2011-10-03T19:51:05.460 に答える
8

Bashの組み込み機能ではそれが可能だとは思いません。

子が終了したときに通知を受け取ることができます:

#!/bin/sh
set -o monitor        # enable script job control
trap 'echo "child died"' CHLD

ただし、シグナル ハンドラで子の終了ステータスを取得する明らかな方法はありません。

その子ステータスを取得することは、通常、wait下位レベルの POSIX API の関数ファミリーの仕事です。残念ながら、これに対する Bash のサポートは限られています。特定の1つの子プロセスを待機する (およびその終了ステータスを取得する) か、すべての子プロセスを待機して常に 0 の結果を取得することができます。

不可能に見えることは、子プロセスが戻るwaitpid(-1)までブロックすると同等です。

于 2008-12-10T14:28:32.417 に答える
7

bash 4.2 以降が利用可能な場合は、以下が役立つ場合があります。連想配列を使用して、タスク名とその「コード」、およびタスク名とその pid を格納します。また、タスクが大量の CPU または I/O 時間を消費し、同時実行タスクの数を制限したい場合に便利な、単純なレート制限方法も組み込みました。

スクリプトは最初のループですべてのタスクを起動し、2 番目のループで結果を消費します。

これは単純なケースでは少しやり過ぎですが、かなりきちんとしたものを可能にします。たとえば、各タスクのエラー メッセージを別の連想配列に格納し、すべてが落ち着いた後に出力することができます。

#! /bin/bash

main () {
    local -A pids=()
    local -A tasks=([task1]="echo 1"
                    [task2]="echo 2"
                    [task3]="echo 3"
                    [task4]="false"
                    [task5]="echo 5"
                    [task6]="false")
    local max_concurrent_tasks=2

    for key in "${!tasks[@]}"; do
        while [ $(jobs 2>&1 | grep -c Running) -ge "$max_concurrent_tasks" ]; do
            sleep 1 # gnu sleep allows floating point here...
        done
        ${tasks[$key]} &
        pids+=(["$key"]="$!")
    done

    errors=0
    for key in "${!tasks[@]}"; do
        pid=${pids[$key]}
        local cur_ret=0
        if [ -z "$pid" ]; then
            echo "No Job ID known for the $key process" # should never happen
            cur_ret=1
        else
            wait $pid
            cur_ret=$?
        fi
        if [ "$cur_ret" -ne 0 ]; then
            errors=$(($errors + 1))
            echo "$key (${tasks[$key]}) failed."
        fi
    done

    return $errors
}

main
于 2013-09-06T21:01:22.220 に答える
6

結果をシェルから、たとえばファイルに保存するだけです。

#!/bin/bash
tmp=/tmp/results

: > $tmp  #clean the file

for i in `seq 0 9`; do
  (doCalculations $i; echo $i:$?>>$tmp)&
done      #iterate

wait      #wait until all ready

sort $tmp | grep -v ':0'  #... handle as required
于 2012-03-20T11:42:08.120 に答える
6

私はこれを試して、ここにある他の例の最良の部分をすべて組み合わせました。このスクリプトは、バックグラウンド プロセスが終了したときに関数を実行し、ポーリングcheckpidsに頼らずに終了ステータスを出力します。

#!/bin/bash

set -o monitor

sleep 2 &
sleep 4 && exit 1 &
sleep 6 &

pids=`jobs -p`

checkpids() {
    for pid in $pids; do
        if kill -0 $pid 2>/dev/null; then
            echo $pid is still alive.
        elif wait $pid; then
            echo $pid exited with zero exit status.
        else
            echo $pid exited with non-zero exit status.
        fi
    done
    echo
}

trap checkpids CHLD

wait
于 2014-02-13T09:37:12.980 に答える
5

スクリプトを変更してバックグラウンドでプロセスを並列化しました。

(Solaris で bash と ksh の両方を使用して) 実験を行ったところ、'wait' がゼロでない場合は終了ステータスを出力すること、または PID 引数が指定されていない場合にゼロ以外の終了を返すジョブのリストが出力されることを発見しました。例えば

バッシュ:

$ sleep 20 && exit 1 &
$ sleep 10 && exit 2 &
$ wait
[1]-  Exit 2                  sleep 20 && exit 2
[2]+  Exit 1                  sleep 10 && exit 1

Ksh:

$ sleep 20 && exit 1 &
$ sleep 10 && exit 2 &
$ wait
[1]+  Done(2)                  sleep 20 && exit 2
[2]+  Done(1)                  sleep 10 && exit 1

この出力は stderr に書き込まれるため、OP の例に対する簡単な解決策は次のようになります。

#!/bin/bash

trap "rm -f /tmp/x.$$" EXIT

for i in `seq 0 9`; do
  doCalculations $i &
done

wait 2> /tmp/x.$$
if [ `wc -l /tmp/x.$$` -gt 0 ] ; then
  exit 1
fi

これの間:

wait 2> >(wc -l)

カウントも返しますが、tmp ファイルはありません。これは、次のように使用することもできます。

wait 2> >(if [ `wc -l` -gt 0 ] ; then echo "ERROR"; fi)

しかし、これは tmp ファイル IMO よりもはるかに便利ではありません。サブシェルで「待機」を実行することを避けながら、tmpファイルを回避する便利な方法を見つけることができませんでした。これはまったく機能しません。

于 2014-12-03T13:01:08.090 に答える
4

ここにはすでに多くの回答がありますが、配列の使用を提案した人が誰もいないことに驚いています...だから、これが私がしたことです-これは将来的に役立つかもしれません。

n=10 # run 10 jobs
c=0
PIDS=()

while true

    my_function_or_command &
    PID=$!
    echo "Launched job as PID=$PID"
    PIDS+=($PID)

    (( c+=1 ))

    # required to prevent any exit due to error
    # caused by additional commands run which you
    # may add when modifying this example
    true

do

    if (( c < n ))
    then
        continue
    else
        break
    fi
done 


# collect launched jobs

for pid in "${PIDS[@]}"
do
    wait $pid || echo "failed job PID=$pid"
done
于 2016-10-28T17:14:07.097 に答える
3
set -e
fail () {
    touch .failure
}
expect () {
    wait
    if [ -f .failure ]; then
        rm -f .failure
        exit 1
    fi
}

sleep 2 || fail &
sleep 2 && false || fail &
sleep 2 || fail
expect

上部にあると、set -e失敗時にスクリプトが停止します。

expect1サブジョブが失敗した場合に返されます。

于 2016-06-24T16:18:02.950 に答える
-1

バックグラウンドに送信されるサブシェルで実行doCalculations; echo "$?" >>/tmp/accされる可能性があると考えています。その後、 1行に1つずつ終了ステータスが含まれます。ただし、複数のプロセスがアキュムレータ ファイルに追加された結果についてはわかりません。wait/tmp/acc

この提案の試行は次のとおりです。

ファイル: doCalcualtions

#!/bin/sh

random -e 20
sleep $?
random -e 10

ファイル: やってみる

#!/bin/sh

rm /tmp/acc

for i in $( seq 0 20 ) 
do
        ( ./doCalculations "$i"; echo "$?" >>/tmp/acc ) &
done

wait

cat /tmp/acc | fmt
rm /tmp/acc

実行中の出力./try

5 1 9 6 8 1 2 0 9 6 5 9 6 0 0 4 9 5 5 9 8
于 2008-12-10T15:11:50.517 に答える