Bashスクリプト内で、そのスクリプトから生成されたいくつかのサブプロセスが終了して終了コード!= 0を返すまで待機する方法
簡単なスクリプト:
#!/bin/bash
for i in `seq 0 9`; do
doCalculations $i &
done
wait
上記のスクリプトは、生成された10個すべてのサブプロセスを待ちますが、常に終了ステータス0を返します(help wait
を参照)。このスクリプトを変更して、生成されたサブプロセスの終了ステータスを検出し、サブプロセスのいずれかがcode!= 0で終了したときに終了コード1を返すようにするにはどうすればよいですか。
サブプロセスのPIDを収集し、順番に待機して終了ステータスを合計するよりも良い解決策はありますか?
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
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
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
これが私がこれまでに思いついたものです。 WAITALL_DELAY
を自分の用法に合わせる必要がないように、子供が終了したらsleepコマンドを中断する方法を見たいと思います。
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
単純にどうですか。
#!/bin/bash
pids=""
for i in `seq 0 9`; do
doCalculations $i &
pids="$pids $!"
done
wait $pids
...code continued here ...
更新:
複数のコメント投稿者が指摘したように、上記はすべてのプロセスが完了するのを待って続行しますが、いずれかが失敗しても終了せず失敗します。@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 ...
これは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.
これを並列化するには.
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 {}
}'
)
--max-procs
は、どれだけの並列処理が必要かに基づいて設定できます(0
は「一度に全部」を意味します)。xargs
の代わりに使用すると、いくつかの追加機能が提供されます - ただし、デフォルトでインストールされるとは限りません。for
ループは、この例では厳密には必要ありません。echo $i
は基本的に単に$(whatever_list
の出力を再生成するためです。 for
キーワードを使用すると、何が起こっているのかが少しわかりやすくなります。これが簡単な作業例です。
for i in {0..5} ; do echo $i ; done |xargs -I{} --max-procs 2 bash -c '
{
echo sleep {}
sleep 2s
}'
Bashの組み込み機能でそれが可能になるとは思わない。
あなたがすることができます子供が終了したときに通知を取得します。
#!/bin/sh
set -o monitor # enable script job control
trap 'echo "child died"' CHLD
しかし、シグナルハンドラで子の終了ステータスを取得する明らかな方法はありません。
その子ステータスを取得するのは、通常、下位レベルのPOSIX APIのwait
ファミリーの関数の仕事です。残念ながらBashのサポートは制限されています - one 特定の子プロセスを待つ(そしてその終了ステータスを取得する)か all を待つことができ、常に0 。
不可能と思われるのは、waitpid(-1)
と同じで、 any childプロセスが返されるまでブロックされます。
私はここにリストされているたくさんの良い例を見ます、私も投げ入れたいと思いました。
#! /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
私は非常によく似た方法でサーバー/サービスを起動/停止し、それぞれの終了ステータスをチェックします。私にとっては素晴らしい作品です。これが誰かに役立つことを願っています!
次のコードはすべての計算が完了するのを待ち、 doCalculations のいずれかが失敗した場合は終了ステータス1を返します。
#!/bin/bash
for i in $(seq 0 9); do
(doCalculations $i >&2 & wait %1; echo $?) &
done | grep -qv 0 && exit 1
これは複数の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
シェルから結果を保存するだけです。ファイル内。
#!/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
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
スクリプトを修正して、プロセスをバックグラウンドで並列化するようにしました。
私は(bashとkshの両方でSolaris上で)いくつかの実験をしましたが、 '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ファイルを避けるための便利な方法を見つけることができませんでしたが、サブシェルで "wait"を実行することも避けていましたが、これはまったくうまくいきませんでした。
#!/bin/bash
set -m
for i in `seq 0 9`; do
doCalculations $i &
done
while fg; do true; done
set -m
はあなたがスクリプトでfgとbgを使うことを可能にしますfg
は、最後のプロセスをフォアグラウンドに置くことに加えて、フォアグラウンドのプロセスと同じ終了ステータスを持ちます。fg
がゼロ以外の終了ステータスで終了すると、while fg
はループを停止します残念ながら、これはバックグラウンドのプロセスが0以外の終了ステータスで終了した場合には処理できません。 (ループはすぐには終了しません。前のプロセスが完了するのを待ちます。)
これはうまくいきます、@ 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コマンドを並行して実行でき、テストに役立ちます。
私はこれを試してみて、ここに他の例からすべての最高の部分を組み合わせました。このスクリプトは any backgroundプロセスが終了すると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
ここにはすでにたくさんの答えがありますが、私は誰も配列の使用を提案していないように思われることに驚いています。
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
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
は失敗したときにあなたのスクリプトを停止させます。
サブジョブが失敗した場合、expect
は1
を返します。
トラップはあなたの友達です。多くのシステムでERRをトラップすることができます。すべてのコマンドの後にコードの一部を実行するために、EXITまたはDEBUGでトラップすることができます。
これはすべての標準シグナルに加えて。
同時に到着した場合、いくつかのシグナルを失う可能性があるため、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 !!!
いくつかのサブプロセスを待機し、それらのいずれかがゼロ以外のステータスコードで終了したときに終了する解決策は、 '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'は、存在しないプロセスのためのものです。これは、子が終了した可能性があることを意味します。
私は最近これを使った(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
そこから簡単に推定することができ、トリガー(ファイルに触れる、シグナルを送る)を持ち、そのトリガーに応答するためにカウント基準(タッチしたファイルなど)を変更することができます。あるいは、 'any'以外のゼロのrcが欲しいのなら、save_statusからロックを解除してください。
これが必要でしたが、ターゲットプロセスは現在のシェルの子ではありませんでした。その場合、wait $PID
は機能しません。代わりに次のような方法を見つけました。
while [ -e /proc/$PID ]; do sleep 0.1 ; done
これは procfs の存在に依存していますが、これは利用できないかもしれません(例えばMacでは提供されていません)。そのため、移植性のために、代わりにこれを使用することができます。
while ps -p $PID >/dev/null ; do sleep 0.1 ; done
これは私が使うものです:
#wait for jobs
for job in `jobs -p`; do wait ${job}; done
ジョブを並行して実行し、ステータスを確認する最も直接的な方法は一時ファイルを使用することです。すでに似たような答えがいくつかあります(例:Nietzche-jouとmug896)。
#!/bin/bash
rm -f fail
for i in `seq 0 9`; do
doCalculations $i || touch fail &
done
wait
! [ -f fail ]
上記のコードはスレッドセーフではありません。上記のコードがそれ自体と同時に実行されるのではないかと心配な場合は、fail。$$のようにもっとユニークなファイル名を使うのが良いでしょう。最後の行は、「サブプロセスのいずれかがcode!= 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
プロセスを待つ前にプロセスが完了している場合があります。既に終了しているプロセスを待つようにトリガすると、pidはこのシェルの子ではないなどのエラーが発生します。このような場合を回避するために、次の機能を使用してプロセスが完了したかどうかを調べることができます。
isProcessComplete(){
PID=$1
while [ -e /proc/$PID ]
do
echo "Process: $PID is still running"
sleep 5
done
echo "Process $PID has finished"
}