いくつかのサブプロセスが終了するまでbashで待機し、サブプロセスがコード!= 0で終了するときに終了コード!= 0を返す方法は?


562

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

簡単なスクリプト:

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

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

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


1
これを大幅に改善してに触れwait -n、最初の/次のコマンドが完了したときにのみ戻るように最新のbashで使用できます。
Charles Duffy

Bashを使用してテストする場合は、次を試してください。github.com
Alexander Mills

2
BATSの積極的な開発はgithub.com/bats-core/bats-coreに
Potherca

3
@CharlesDuffyにwait -nは1つの小さな問題があります。子ジョブが残っていない場合(競合状態)、ゼロ以外の終了ステータス(失敗)を返します。これは、失敗した子プロセスと区別できない場合があります。
drevicko

5
@CharlesDuffy-あなたは素晴らしい洞察力を持ち、それを共有することでSOに巨大なサービスを提供します。私が読んだSO投稿の約80%は、広大な経験の海から来ているはずのコメントで、知識のすばらしい小さなダイヤモンドを共有しているようです。どうもありがとう!
Brett Holman

回答:


520

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

9
さて、あなたはすべてのプロセスを待つつもりなので、たとえば、2番目のプロセスがすでに終了している間に最初のプロセスを待っているかどうかは問題ではありません(2番目のプロセスはとにかく次の反復で選択されます)。これは、Cでwait(2)を使用するのと同じアプローチです。
Luca Tettamanti 2008

7
ああ、わかりました-別の解釈:) 「サブプロセスのいずれかが終了するとすぐに終了コード1を返す」という意味として質問を読みました。
Alnitak

56
PIDは実際に再利用できますが、現在のプロセスの子ではないプロセスを待つことはできません(その場合、待機は失敗します)。
tkokoszka 2008

12
%nを使用してn:番目のバックグラウンドジョブを参照し、%%を使用して最新のジョブを参照することもできます。
10

30
@Nils_M:その通りです、ごめんなさい。したがって、それは次のようになりfor i in $n_procs; do ./procs[${i}] & ; pids[${i}]=$!; done; for pid in ${pids[*]}; do wait $pid; done;ます。
synack 2014年

284

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

103
jobs -p実行状態にあるサブプロセスのPIDを提供しています。プロセスjobs -pが呼び出される前にプロセスが終了した場合、プロセスをスキップします。したがって、サブプロセスのいずれかがの前jobs -pに終了した場合、そのプロセスの終了ステータスは失われます。
tkokoszka 2009

15
うわー、この答えは最高評価のものよりもはるかに優れています。:/
e40 2012年

4
@ e40と以下の答えはおそらくさらに良いでしょう。また、各コマンドを '(cmd; echo "$?" >> "$ tmpfile")で実行し、この待機を使用して、失敗したファイルを読み取ることをお勧めします。また、注釈出力。…または、あまり気にしないときにこのスクリプトを使用します。
HoverHell 2012年

この回答は承認された回答よりも優れていることを付け加えたいと思います
shurikk

2
@tkokoszkaが正確であることjobs -pは、サブプロセスのPIDではなく、GPIDを提供することです。待機ロジックはとにかく機能しているようですが、そのようなグループが存在する場合は常にグループで待機し、存在しない場合はpidで待機しますが、特にこれに基づいて、サブプロセスにメッセージを送信するようなものを組み込む場合は注意してください。構文は、PIDまたはGPIDがあるかどうかによって異なります。つまり、kill -- -$GPIDvskill $PID
Timo

58

以下は、を使用しwaitた簡単な例です。

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

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

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

$ wait < <(jobs -p)

または単にwait(引数なしで)すべてに対して。

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

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

構文:help waithelp jobsを参照してください。

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

または、計算関数を作成して、失敗時にファイルを作成し(空または失敗ログあり)、そのファイルが存在するかどうかを確認します(例:

$ sleep 20 && true || tee fail &
$ sleep 20 && false || tee fail &
$ wait < <(jobs -p)
$ test -f fail && echo Calculation failed.

1
bashの初心者のために、この例の2つの計算はsleep 20 && trueand sleep 20 && falseです。つまり、それらを関数に置き換えます。&&and を理解するには||、実行man bashして「/」(検索)、「^ *リスト」(正規表現)の順に入力し、次のように入力します&&||
。man

1
ファイル 'fail'が最初に存在しないことを確認する(または削除する)必要があります。アプリケーションによっては、「2>&1」を追加して||からSTDERRの失敗をキャッチすることをお勧めします。
drevicko

私はこれが好きです、欠点はありますか?実際には、すべてのサブプロセスを一覧表示して、いくつかのアクションを実行したい場合のみです。シグナルを送信します。pidの簿記またはジョブの反復を試行します。終了を待って、ちょうどwait
xgwang

これは、jobs -pが呼び出される前に失敗したジョブの終了ステータスを見逃します
Erik Aronesty

50

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


1
ありがとう!しかし、あなたが私が後に陥った「混乱」の問題について言及するのを忘れていました:unix.stackexchange.com/a/35953
nobar

1
これは優れたツールのように見えますが、doCalculations同じスクリプトで定義された関数であるBashスクリプトでは、上記はそのままでは機能しないと思います(ただし、OPはこの要件について明確ではありませんでした)。私が試してみると、parallelこう言われます/bin/bash: doCalculations: command not foundseq 0 9上の例では、これが10回表示されます)。回避策については、こちらをご覧ください。
nobar 2013年

3
また興味深い:オプションxargsを介してジョブを並行して起動する機能があり-Pます。ここから:export -f doCalculations ; seq 0 9 |xargs -P 0 -n 1 -I{} bash -c "doCalculations {}"。の制限はxargs、のマニュアルページに記載されていparallelます。
nobar

また、doCalculations他のスクリプト内部環境変数(カスタムPATHなど)に依存している場合は、export起動する前に明示的に編集する必要がありますparallel
nobar 2013年

4
@nobar混乱は、一部のパッケージャーがユーザーのために物事を台無しにしているためです。を使用してインストールする場合wget -O - pi.dk/3 | sh、混乱はありません。あなたのパッケージャーがあなたのために物事を台無しにした場合、私はあなたのパッケージャーで問題を提起することをお勧めします。変数と関数(参照GNUそれらを見るために並行ための(輸出-f)をエクスポートする必要がありますman parallelgnu.org/software/parallel/...を
オレ丹下

46

単純にどうですか:

#!/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 ...

1
待機のマニュアルページによると、複数のPIDで待機すると、最後に待機したプロセスの戻り値のみが返されます。したがって、受け入れられた回答(コメント)で提案されているように、追加のループが必要で、各PIDを個別に待機する必要があります。
Vlad Frolov

1
このページの他のどこにも記載されていないようなので、ループは次のようになると付け加えますfor pid in $pids; do wait $pid; done
Bryan

1
@bisounours_tronconneuseはい、そうです。参照してくださいhelp wait-複数のIDをwait@ヴラド・フロロフは、上記の言ったように、最後のものだけの終了コードを返します。
サムブライトマン

1
ブライアン、@ SamBrightman OK。私はあなたの推薦でそれを修正しました。
patapouf_ai 2016

4
私はこのソリューションに明らかな懸念を持っていました:対応するものwaitが呼び出される前に特定のプロセスが終了した場合はどうなりますか?これは問題ではないことがわかります。waitすでに終了しているプロセスを実行している場合waitは、すでに終了しているプロセスのステータスですぐに終了します。(bash作者、ありがとう!)
Daniel Griscom

39

これが私がこれまでに思いついたことです。子供が終了した場合にスリープコマンドを中断する方法を確認したいので、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

ループ内でプロセスが開始されないので、WAITALL_DELAYをスキップするか、非常に低く設定する可能性があります。高すぎるとは思いません。
マリアン

21

これを並列化するには...

for i in $(whatever_list) ; do
   do_something $i
done

これに翻訳...

for i in $(whatever_list) ; do echo $i ; done | ## execute in parallel...
   (
   export -f do_something ## export functions (if needed)
   export PATH ## export any variables that are required
   xargs -I{} --max-procs 0 bash -c ' ## process in batches...
      {
      echo "processing {}" ## optional
      do_something {}
      }' 
   )
  • 1つのプロセスでエラーが発生した場合、他のプロセスは中断されませんが、シーケンス全体としてゼロ以外の終了コードが発生します
  • 特定のケースでは、関数と変数のエクスポートが必要な場合と必要でない場合があります。
  • --max-procs必要な並列処理に基づいて設定できます(0「一度にすべて」を意味します)。
  • GNU Parallel代わりに使用すると、いくつかの追加機能が提供xargsされますが、デフォルトで常にインストールされるわけではありません。
  • forこの例では、ループは厳密には必要ありません。echo $i基本的にだけの出力を再生されます$(whatever_list)。forキーワードを使用すると、何が起こっているのかが少しわかりやすくなると思います。
  • Bash文字列の処理は混乱を招く可能性があります-一重引用符を使用すると、重要なスクリプトをラップするのに最適です。
  • Bash並列処理へのより直接的なアプローチとは異なり、操作全体を(^ Cなどを使用して)簡単に中断できます。

ここに単純化された作業例があります...

for i in {0..5} ; do echo $i ; done |xargs -I{} --max-procs 2 bash -c '
   {
   echo sleep {}
   sleep 2s
   }'


7

Bashの組み込み機能でそれが可能であるとは思わない。

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

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

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

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

何それを行うことは不可能見えることと等価であるwaitpid(-1)までどのブロック、任意の子プロセスが戻ります。


7

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

#! /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

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


Ctrl + CIで停止しても、バックグラウンドで実行中のプロセスが表示されます。
カルステン2018

2
@karsten-これは別の問題です。bashを使用しているとすると、終了条件(Ctrl + Cを含む)をトラップして、現在およびすべての子プロセスtrap "kill 0" EXIT
Phil

@Philは正しいです。これらはバックグラウンドプロセスであるため、親プロセスを強制終了しても、子プロセスは実行されたままになります。私の例では、Philが述べたように必要に応じて追加できるシグナルをトラップしません。
Jason Slobotski、

6

これは私が使用するものです:

#wait for jobs
for job in `jobs -p`; do wait ${job}; done

5

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

#!/bin/bash
for i in $(seq 0 9); do
   (doCalculations $i >&2 & wait %1; echo $?) &
done | grep -qv 0 && exit 1

5

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

#!/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

5

これは、複数のPIDで機能する私のバージョンです。実行に時間がかかりすぎる場合は警告をログに記録し、実行が指定された値よりも長い場合はサブプロセスを停止します。

function WaitForTaskCompletion {
    local pids="${1}" # pids to wait for, separated by semi-colon
    local soft_max_time="${2}" # If execution takes longer than $soft_max_time seconds, will log a warning, unless $soft_max_time equals 0.
    local hard_max_time="${3}" # If execution takes longer than $hard_max_time seconds, will stop execution, unless $hard_max_time equals 0.
    local caller_name="${4}" # Who called this function
    local exit_on_error="${5:-false}" # Should the function exit program on subprocess errors       

    Logger "${FUNCNAME[0]} called by [$caller_name]."

    local soft_alert=0 # Does a soft alert need to be triggered, if yes, send an alert once 
    local log_ttime=0 # local time instance for comparaison

    local seconds_begin=$SECONDS # Seconds since the beginning of the script
    local exec_time=0 # Seconds since the beginning of this function

    local retval=0 # return value of monitored pid process
    local errorcount=0 # Number of pids that finished with errors

    local pidCount # number of given pids

    IFS=';' read -a pidsArray <<< "$pids"
    pidCount=${#pidsArray[@]}

    while [ ${#pidsArray[@]} -gt 0 ]; do
        newPidsArray=()
        for pid in "${pidsArray[@]}"; do
            if kill -0 $pid > /dev/null 2>&1; then
                newPidsArray+=($pid)
            else
                wait $pid
                result=$?
                if [ $result -ne 0 ]; then
                    errorcount=$((errorcount+1))
                    Logger "${FUNCNAME[0]} called by [$caller_name] finished monitoring [$pid] with exitcode [$result]."
                fi
            fi
        done

        ## Log a standby message every hour
        exec_time=$(($SECONDS - $seconds_begin))
        if [ $((($exec_time + 1) % 3600)) -eq 0 ]; then
            if [ $log_ttime -ne $exec_time ]; then
                log_ttime=$exec_time
                Logger "Current tasks still running with pids [${pidsArray[@]}]."
            fi
        fi

        if [ $exec_time -gt $soft_max_time ]; then
            if [ $soft_alert -eq 0 ] && [ $soft_max_time -ne 0 ]; then
                Logger "Max soft execution time exceeded for task [$caller_name] with pids [${pidsArray[@]}]."
                soft_alert=1
                SendAlert

            fi
            if [ $exec_time -gt $hard_max_time ] && [ $hard_max_time -ne 0 ]; then
                Logger "Max hard execution time exceeded for task [$caller_name] with pids [${pidsArray[@]}]. Stopping task execution."
                kill -SIGTERM $pid
                if [ $? == 0 ]; then
                    Logger "Task stopped successfully"
                else
                    errrorcount=$((errorcount+1))
                fi
            fi
        fi

        pidsArray=("${newPidsArray[@]}")
        sleep 1
    done

    Logger "${FUNCNAME[0]} ended for [$caller_name] using [$pidCount] subprocesses with [$errorcount] errors."
    if [ $exit_on_error == true ] && [ $errorcount -gt 0 ]; then
        Logger "Stopping execution."
        exit 1337
    else
        return $errorcount
    fi
}

# Just a plain stupid logging function to replace with yours
function Logger {
    local value="${1}"

    echo $value
}

たとえば、3つのプロセスすべてが完了するまで待機し、実行に5秒以上かかる場合は警告をログに記録し、実行に120秒以上かかる場合はすべてのプロセスを停止します。失敗してもプログラムを終了しません。

function something {

    sleep 10 &
    pids="$!"
    sleep 12 &
    pids="$pids;$!"
    sleep 9 &
    pids="$pids;$!"

    WaitForTaskCompletion $pids 5 120 ${FUNCNAME[0]} false
}
# Launch the function
someting

4

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

4

スクリプトをバックグラウンドに変更して、プロセスを並列化しているところです。

私はいくつかの実験(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ファイルを回避するための便利な方法を見つけることができませんでした。


3

私はこれで試してみて、ここにある他の例のすべての最良の部分を組み合わせました。このスクリプトはバックグラウンドプロセスが終了すると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

3
#!/bin/bash
set -m
for i in `seq 0 9`; do
  doCalculations $i &
done
while fg; do true; done
  • set -m スクリプトでfg&bgを使用できます
  • fg、最後のプロセスをフォアグラウンドに置くことに加えて、フォアグラウンドのプロセスと同じ終了ステータスを持っています
  • while fgfgゼロ以外の終了ステータスで終了するとループが停止します

残念ながら、これは、バックグラウンドのプロセスがゼロ以外の終了ステータスで終了する場合を処理しません。(ループはすぐには終了しません。前のプロセスが完了するまで待機します。)


3

ここにはすでにたくさんの答えがありますが、配列の使用を提案しているように思われる人がいないことに驚いています...だから、私がやったことはこれです-これは将来的に役立つかもしれません。

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

3

これは機能しますが、@ HoverHellの回答よりも優れているとは限りません。

#!/usr/bin/env bash

set -m # allow for job control
EXIT_CODE=0;  # exit code of overall script

function foo() {
     echo "CHLD exit code is $1"
     echo "CHLD pid is $2"
     echo $(jobs -l)

     for job in `jobs -p`; do
         echo "PID => ${job}"
         wait ${job} ||  echo "At least one test failed with exit code => $?" ; EXIT_CODE=1
     done
}

trap 'foo $? $$' CHLD

DIRN=$(dirname "$0");

commands=(
    "{ echo "foo" && exit 4; }"
    "{ echo "bar" && exit 3; }"
    "{ echo "baz" && exit 5; }"
)

clen=`expr "${#commands[@]}" - 1` # get length of commands - 1

for i in `seq 0 "$clen"`; do
    (echo "${commands[$i]}" | bash) &   # run the command via bash in subshell
    echo "$i ith command has been issued as a background job"
done

# wait for all to finish
wait;

echo "EXIT_CODE => $EXIT_CODE"
exit "$EXIT_CODE"

# end

そしてもちろん、私はこのスクリプトを不滅にしました。NPMプロジェクトでは、bashコマンドを並行して実行できるため、テストに役立ちます。

https://github.com/ORESoftware/generic-subshel​​l


trap $? $$私にとって毎回、終了コードを0に、PIDを現在実行中のbashシェルに設定しているようです
inetknght

あなたは絶対にそれについて確信していますか?それが理にかなっているかどうかはわかりません。
Alexander Mills

2

トラップはあなたの友達です。多くのシステムでERRをトラップできます。EXITをトラップするか、DEBUGでトラップして、すべてのコマンドの後にコードを実行できます。

これは、すべての標準信号に加えて。


1
いくつかの例を挙げて、答えを詳しく説明してください。
ϹοδεMεδιϲ

2
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サブジョブが失敗した場合に戻ります。


2

この目的のために、私はというbash関数を書きました:for

:forは、失敗した関数の終了コードを保持して返すだけでなく、並列実行中のすべてのインスタンスを終了します。この場合、これは必要ないかもしれません。

#!/usr/bin/env bash

# Wait for pids to terminate. If one pid exits with
# a non zero exit code, send the TERM signal to all
# processes and retain that exit code
#
# usage:
# :wait 123 32
function :wait(){
    local pids=("$@")
    [ ${#pids} -eq 0 ] && return $?

    trap 'kill -INT "${pids[@]}" &>/dev/null || true; trap - INT' INT
    trap 'kill -TERM "${pids[@]}" &>/dev/null || true; trap - RETURN TERM' RETURN TERM

    for pid in "${pids[@]}"; do
        wait "${pid}" || return $?
    done

    trap - INT RETURN TERM
}

# Run a function in parallel for each argument.
# Stop all instances if one exits with a non zero
# exit code
#
# usage:
# :for func 1 2 3
#
# env:
# FOR_PARALLEL: Max functions running in parallel
function :for(){
    local f="${1}" && shift

    local i=0
    local pids=()
    for arg in "$@"; do
        ( ${f} "${arg}" ) &
        pids+=("$!")
        if [ ! -z ${FOR_PARALLEL+x} ]; then
            (( i=(i+1)%${FOR_PARALLEL} ))
            if (( i==0 )) ;then
                :wait "${pids[@]}" || return $?
                pids=()
            fi
        fi
    done && [ ${#pids} -eq 0 ] || :wait "${pids[@]}" || return $?
}

使用法

for.sh

#!/usr/bin/env bash
set -e

# import :for from gist: https://gist.github.com/Enteee/c8c11d46a95568be4d331ba58a702b62#file-for
# if you don't like curl imports, source the actual file here.
source <(curl -Ls https://gist.githubusercontent.com/Enteee/c8c11d46a95568be4d331ba58a702b62/raw/)

msg="You should see this three times"

:(){
  i="${1}" && shift

  echo "${msg}"

  sleep 1
  if   [ "$i" == "1" ]; then sleep 1
  elif [ "$i" == "2" ]; then false
  elif [ "$i" == "3" ]; then
    sleep 3
    echo "You should never see this"
  fi
} && :for : 1 2 3 || exit $?

echo "You should never see this"
$ ./for.sh; echo $?
You should see this three times
You should see this three times
You should see this three times
1

参考文献


1

私は最近これを使用しました(Alnitakに感謝):

#!/bin/bash
# activate child monitoring
set -o monitor

# locking subprocess
(while true; do sleep 0.001; done) &
pid=$!

# count, and kill when all done
c=0
function kill_on_count() {
    # you could kill on whatever criterion you wish for
    # I just counted to simulate bash's wait with no args
    [ $c -eq 9 ] && kill $pid
    c=$((c+1))
    echo -n '.' # async feedback (but you don't know which one)
}
trap "kill_on_count" CHLD

function save_status() {
    local i=$1;
    local rc=$2;
    # do whatever, and here you know which one stopped
    # but remember, you're called from a subshell
    # so vars have their values at fork time
}

# care must be taken not to spawn more than one child per loop
# e.g don't use `seq 0 9` here!
for i in {0..9}; do
    (doCalculations $i; save_status $i $?) &
done

# wait for locking subprocess to be killed
wait $pid
echo

そこから、簡単に外挿してトリガーを設定し(ファイルに触れ、信号を送信)、そのトリガーに応答するためにカウント基準(タッチされたファイルなど)を変更できます。または、0以外の「任意の」rcが必要な場合は、save_statusからロックを強制終了します。


1

私はこれを必要としましたが、ターゲットプロセスは現在のシェルの子ではなく、その場合wait $PIDは機能しません。代わりに次の代替案を見つけました。

while [ -e /proc/$PID ]; do sleep 0.1 ; done

これは、利用できない可能性があるprocfsの存在に依存します(たとえば、Macでは提供されていません)。したがって、移植性のために、代わりにこれを使用できます。

while ps -p $PID >/dev/null ; do sleep 0.1 ; done

1

CHLDシグナルが同時に到着した場合、一部のシグナルが失われる可能性があるため、CHLDシグナルのトラップが機能しない場合があります。

#!/bin/bash

trap 'rm -f $tmpfile' EXIT

tmpfile=$(mktemp)

doCalculations() {
    echo start job $i...
    sleep $((RANDOM % 5)) 
    echo ...end job $i
    exit $((RANDOM % 10))
}

number_of_jobs=10

for i in $( seq 1 $number_of_jobs )
do
    ( trap "echo job$i : exit value : \$? >> $tmpfile" EXIT; doCalculations ) &
done

wait 

i=0
while read res; do
    echo "$res"
    let i++
done < "$tmpfile"

echo $i jobs done !!!

1

複数のサブプロセスを待機し、それらのいずれかがゼロ以外のステータスコードで終了したときに終了するソリューションは、 'wait -n'を使用することです。

#!/bin/bash
wait_for_pids()
{
    for (( i = 1; i <= $#; i++ )) do
        wait -n $@
        status=$?
        echo "received status: "$status
        if [ $status -ne 0 ] && [ $status -ne 127 ]; then
            exit 1
        fi
    done
}

sleep_for_10()
{
    sleep 10
    exit 10
}

sleep_for_20()
{
    sleep 20
}

sleep_for_10 &
pid1=$!

sleep_for_20 &
pid2=$!

wait_for_pids $pid2 $pid1

ステータスコード「127」は存在しないプロセス用であり、子プロセスが終了した可能性があります。


1

すべてのジョブを待ち、最後に失敗したジョブの終了コードを返します。上記のソリューションとは異なり、これはpidの保存を必要としません。ちょっと離れて、待ってください。

function wait_ex {
    # this waits for all jobs and returns the exit code of the last failing job
    ecode=0
    while true; do
        wait -n
        err="$?"
        [ "$err" == "127" ] && break
        [ "$err" != "0" ] && ecode="$err"
    done
    return $ecode
}

これは機能し、「コマンドが見つかりません」(コード127)でない限り、実行されたコマンドから最初のエラーコードを確実に提供します。
drevicko

0

プロセスを待つ前にプロセスが完了する場合があります。すでに終了しているプロセスの待機をトリガーすると、pidがこのシェルの子ではないなどのエラーがトリガーされます。このような場合を回避するために、次の関数を使用して、プロセスが完了したかどうかを確認できます。

isProcessComplete(){
PID=$1
while [ -e /proc/$PID ]
do
    echo "Process: $PID is still running"
    sleep 5
done
echo "Process $PID has finished"
}

0

ジョブを並行して実行してステータスを確認する最も簡単な方法は、一時ファイルを使用することだと思います。同様の回答がいくつかあります(例:Nietzche-jouとmug896)。

#!/bin/bash
rm -f fail
for i in `seq 0 9`; do
  doCalculations $i || touch fail &
done
wait 
! [ -f fail ]

上記のコードはスレッドセーフではありません。上記のコードが同時に実行されることが心配な場合は、fail。$$のように、より一意のファイル名を使用することをお勧めします。最後の行は要件を満たすことです:「サブプロセスのいずれかがコード!= 0で終了したときに終了コード1を返す」私は片付けをするためにそこに追加の要求を投げました。次のように書くほうがより明確だったかもしれません:

#!/bin/bash
trap 'rm -f fail.$$' EXIT
for i in `seq 0 9`; do
  doCalculations $i || touch fail.$$ &
done
wait 
! [ -f fail.$$ ] 

複数のジョブから結果を収集するための同様のスニペットを次に示します。一時ディレクトリを作成し、すべてのサブタスクの出力を別のファイルに記録して、確認のためにダンプします。これは実際には質問と一致しません-私はそれをボーナスとして投入しています:

#!/bin/bash
trap 'rm -fr $WORK' EXIT

WORK=/tmp/$$.work
mkdir -p $WORK
cd $WORK

for i in `seq 0 9`; do
  doCalculations $i >$i.result &
done
wait 
grep $ *  # display the results with filenames and contents

0

jobs -p以下のスクリプトに示すように、私はPIDを収集するためにを使用するという罠に陥りそうになりました。これは、子供がすでに終了している場合には機能しません。私が選んだ解決策は、単にwait -nN回呼び出すことでした。ここで、Nは私が持っている子供の数であり、たまたま私は決定論的に知っています。

#!/usr/bin/env bash

sleeper() {
    echo "Sleeper $1"
    sleep $2
    echo "Exiting $1"
    return $3
}

start_sleepers() {
    sleeper 1 1 0 &
    sleeper 2 2 $1 &
    sleeper 3 5 0 &
    sleeper 4 6 0 &
    sleep 4
}

echo "Using jobs"
start_sleepers 1

pids=( $(jobs -p) )

echo "PIDS: ${pids[*]}"

for pid in "${pids[@]}"; do
    wait "$pid"
    echo "Exit code $?"
done

echo "Clearing other children"
wait -n; echo "Exit code $?"
wait -n; echo "Exit code $?"

echo "Waiting for N processes"
start_sleepers 2

for ignored in $(seq 1 4); do
    wait -n
    echo "Exit code $?"
done

出力:

Using jobs
Sleeper 1
Sleeper 2
Sleeper 3
Sleeper 4
Exiting 1
Exiting 2
PIDS: 56496 56497
Exiting 3
Exit code 0
Exiting 4
Exit code 0
Clearing other children
Exit code 0
Exit code 1
Waiting for N processes
Sleeper 1
Sleeper 2
Sleeper 3
Sleeper 4
Exiting 1
Exiting 2
Exit code 0
Exit code 2
Exiting 3
Exit code 0
Exiting 4
Exit code 0
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.