How to wait in bash for several subprocesses to finish and return exit code !=0 when any subprocess ends with code !=0?
如何在bash脚本中等待该脚本生成的几个子进程完成并返回退出代码!=0,当任何子进程以代码结束时!= 0?
简单脚本:
1 2 3 4 5 | #!/bin/bash for i in `seq 0 9`; do doCalculations $i & done wait |
上面的脚本将等待所有生成的10个子进程,但它始终给出退出状态0(参见
有没有比收集子流程的PID、按顺序等待它们并求和退出状态更好的解决方案?
1 2 3 4 5 6 7 8 9 10 | # 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:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 | #!/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,可以执行以下操作:
1 2 3 | # 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 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 | 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 |
简单地说:
1 2 3 4 5 6 7 8 9 10 11 12 | #!/bin/bash pids="" for i in `seq 0 9`; do doCalculations $i & pids="$pids $!" done wait $pids ...code continued here ... |
更新:
正如多位评论者所指出的,上述流程在继续之前等待所有流程完成,但如果其中一个流程失败,则不会退出并失败,可以通过@brian、@sambrightman和其他人建议的以下修改来完成:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 | #!/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 2 3 4 | $ sleep 10 & $ sleep 10 & $ sleep 20 & $ sleep 20 & |
然后用
1 | $ wait < <(jobs -p) |
或者只是为了所有人(没有理由)的利益。
这将等待后台的所有作业完成。
如果提供了
语法见:
但是,缺点是它只返回最后一个ID的状态,因此您需要检查每个子进程的状态并将其存储在变量中。
或者让您的计算函数在失败时创建一些文件(空的或有失败日志),然后检查该文件是否存在,例如。
1 2 3 4 | $ sleep 20 && true || tee fail & $ sleep 20 && false || tee fail & $ wait < <(jobs -p) $ test -f fail && echo Calculation failed. |
要将其并行化…
1 2 3 | for i in $(whatever_list) ; do do_something $i done |
翻译成这个…
1 2 3 4 5 6 7 8 9 10 | 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 表示"一次完成")。 - 当用GNU Parallel代替
xargs 时,它提供了一些额外的功能——但默认情况下并不总是安装它。 - 在这个例子中,不需要使用
for 循环,因为echo $i 基本上只是重新生成$(whatever_list 的输出。我只是认为使用for 关键字可以让您更容易地看到正在发生的事情。 - bash字符串处理可能会令人困惑——我发现使用单引号最适合包装非琐碎的脚本。
- 您可以轻松地中断整个操作(使用^C或类似方法),这与更直接的bash并行性方法不同。
下面是一个简单的工作示例…
1 2 3 4 5 | for i in {0..5} ; do echo $i ; done |xargs -I{} --max-procs 2 bash -c ' { echo sleep {} sleep 2s }' |
我不相信使用bash的内置功能是可能的。
当子项退出时,您可以收到通知:
1 2 3 | #!/bin/sh set -o monitor # enable script job control trap 'echo"child died"' CHLD |
但是,在信号处理程序中没有明显的方法来获取子进程的退出状态。
在较低级别的POSIX API中,获取子状态通常是
似乎不可能做到的是等效于
我看到这里列出了很多很好的例子,我也想把我的例子也扔进去。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | #! /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 |
我使用非常类似的东西来并行启动/停止服务器/服务,并检查每个退出状态。对我来说很好。希望这能帮助别人!
以下代码将等待所有计算完成,如果任何文档计算失败,则返回退出状态1。
1 2 3 4 | #!/bin/bash for i in $(seq 0 9); do (doCalculations $i >&2 & wait %1; echo $?) & done | grep -qv 0 && exit 1 |
这是我的版本,适用于多个PID,如果执行时间过长,则记录警告,如果执行时间长于给定值,则停止子流程。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 | 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 } |
例如,等待所有三个进程完成,如果执行时间超过5秒,则记录一条警告;如果执行时间超过120秒,则停止所有进程。失败时不要退出程序。
1 2 3 4 5 6 7 8 9 10 11 12 13 | function something { sleep 10 & pids="$!" sleep 12 & pids="$pids;$!" sleep 9 & pids="$pids;$!" WaitForTaskCompletion $pids 5 120 ${FUNCNAME[0]} false } # Launch the function someting |
只需将结果存储在外壳中,例如存储在文件中。
1 2 3 4 5 6 7 8 9 10 11 12 | #!/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时间,并且您想要限制并发任务的数量,那么这个方法可能会很方便。
脚本在第一个循环中启动所有任务,并在第二个循环中使用结果。
对于简单的案例来说,这有点过分,但它允许使用非常整洁的东西。例如,可以将每个任务的错误消息存储在另一个关联数组中,并在所有问题都解决后打印它们。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 | #! /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参数时返回非零退出的作业列表。例如。
猛击:
1 2 3 4 5 | $ sleep 20 && exit 1 & $ sleep 10 && exit 2 & $ wait [1]- Exit 2 sleep 20 && exit 2 [2]+ Exit 1 sleep 10 && exit 1 |
Ksh:
1 2 3 4 5 | $ sleep 20 && exit 1 & $ sleep 10 && exit 2 & $ wait [1]+ Done(2) sleep 20 && exit 2 [2]+ Done(1) sleep 10 && exit 1 |
此输出被写入stderr,因此ops示例的简单解决方案可以是:
1 2 3 4 5 6 7 8 9 10 11 12 | #!/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 |
而这一点:
1 | wait 2> >(wc -l) |
也将返回一个计数,但不返回tmp文件。也可以这样使用,例如:
1 | wait 2> >(if [ `wc -l` -gt 0 ] ; then echo"ERROR"; fi) |
但是,这并没有比tmp文件imo更有用。我找不到一种有效的方法来避免tmp文件,同时也避免在一个根本不起作用的子shell中运行"wait"。
我已经尝试过了,并结合了这里其他例子中所有最好的部分。此脚本将在任何后台进程退出时执行
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 | #!/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 |
这是可行的,如果不是比@hoverhell的答案更好的话,也应该是一个不错的答案!
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 | #!/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-subshell
1 2 3 4 5 6 | #!/bin/bash set -m for i in `seq 0 9`; do doCalculations $i & done while fg; do true; done |
set -m 允许您在脚本中使用fg&bgfg 除了将最后一个进程置于前台之外,还具有与它所预期的进程相同的退出状态。- 当任何一个出口状态为非零时,
while fg 将停止循环。
不幸的是,当后台进程以非零退出状态退出时,这将无法处理这种情况。(循环不会立即终止。它将等待上一个进程完成。)
陷阱是你的朋友。在许多系统中,您可以捕获错误。您可以陷阱退出,或者在调试时在每个命令之后执行一段代码。
除了所有的标准信号。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | 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 |
顶部的
如果任何子作业失败,
这里已经有很多答案了,但我很惊讶似乎没有人建议使用数组…这就是我所做的-这可能对将来的一些人有用。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 | 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 |
我需要这个,但目标进程不是当前shell的子进程,在这种情况下,
1 | while [ -e /proc/$PID ]; do sleep 0.1 ; done |
这取决于procfs的存在,而procfs可能不可用(例如,mac不提供)。因此,对于可移植性,您可以使用它:
1 | while ps -p $PID >/dev/null ; do sleep 0.1 ; done |
我最近用过这个(多亏了阿尼塔克):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 | #!/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_状态取消锁定即可。
捕获chld信号可能不起作用,因为如果它们同时到达,您可能会丢失一些信号。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 | #!/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"可以等待多个子流程,并在其中任何一个子流程退出时退出(状态代码为非零)。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 | #!/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 2 | #wait for jobs for job in `jobs -p`; do wait ${job}; done |
在等待进程之前,可能存在进程已完成的情况。如果我们触发wait等待一个已经完成的进程,它将触发一个错误,比如pid不是这个shell的子进程。为了避免这种情况,可以使用以下功能来确定流程是否完整:
1 2 3 4 5 6 7 8 9 | isProcessComplete(){ PID=$1 while [ -e /proc/$PID ] do echo"Process: $PID is still running" sleep 5 done echo"Process $PID has finished" } |
我认为最直接的并行运行作业和检查状态的方法是使用临时文件。已经有几个类似的答案(例如,Nietzche-Jou和Mug896)。
1 2 3 4 5 6 7 | #!/bin/bash rm -f fail for i in `seq 0 9`; do doCalculations $i || touch fail & done wait ! [ -f fail ] |
上面的代码不是线程安全的。如果您担心上面的代码将与它本身同时运行,那么最好使用一个更唯一的文件名,如fail。最后一行是要满足这样的要求:"当任何子流程以代码结束时,返回退出代码1!"= 0?"我在那里提出了一个额外的清理要求。这样写可能更清楚:
1 2 3 4 5 6 7 | #!/bin/bash trap 'rm -f fail.$$' EXIT for i in `seq 0 9`; do doCalculations $i || touch fail.$$ & done wait ! [ -f fail.$$ ] |
下面是一个从多个作业收集结果的类似片段:我创建一个临时目录,将所有子任务的输出记录在一个单独的文件中,然后将它们转储以供审阅。这与问题不符-我把它作为奖励:
1 2 3 4 5 6 7 8 9 10 11 12 | #!/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 |
我想也许可以运行文档计算;echo"$?">>/tmp/acc在发送到后台的子shell中,然后等待,然后/tmp/acc将包含退出状态,每行一个。不过,我不知道附加到收集器文件的多个进程的任何后果。
下面是这个建议的试用版:
文件:文档
1 2 3 4 5 | #!/bin/sh random -e 20 sleep $? random -e 10 |
文件:尝试
1 2 3 4 5 6 7 8 9 10 11 12 13 | #!/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 |
运行输出。/尝试
1 | 5 1 9 6 8 1 2 0 9 6 5 9 6 0 0 4 9 5 5 9 8 |