如何在bash中等待多个子进程完成并返回退出代码!= 0当任何子进程以代码结束时!= 0?

tko*_*zka 518 bash process wait

如何在bash脚本中等待从该脚本生成的几个子进程完成并返回退出代码!= 0当任何子进程以代码结束时!= 0?

简单的脚本:

#!/bin/bash
for i in `seq 0 9`; do
  doCalculations $i &
done
wait
Run Code Online (Sandbox Code Playgroud)

上面的脚本将等待所有10个生成的子进程,但它总是会给出退出状态0(请参阅参考资料help wait).如何修改此脚本,以便它发现生成的子进程的退出状态,并在任何子进程以代码!= 0结束时返回退出代码1?

有没有更好的解决方案,而不是收集子流程的PID,按顺序等待它们并总结退出状态?

Luc*_*nti 471

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
Run Code Online (Sandbox Code Playgroud)

  • PID可以确实重用,但是你不能等待一个不是当前进程子进程的进程(在这种情况下等待失败). (56认同)
  • @Nils_M:你说对了,对不起.所以它会是这样的:`for $ in $ n_procs; do ./procs[${i}]&; 的PID [$ {I}] = $ !; 完成; for pid in $ {pids [*]}; 等待$ pid; 完了;`,对吗? (30认同)
  • 您也可以使用%n来引用第n个后台作业,使用%%来引用最近的作业. (11认同)
  • Weel,因为你要等待所有的过程并不重要,例如你在等待第一个过程而第二个已经完成(第二个将在下一个迭代中被选中).这与你在C中使用wait(2)的方法相同. (8认同)
  • 啊,我明白了 - 不同的解释:)我把这个问题读成"当任何子进程退出时返回退出代码1 _immediately_". (6认同)
  • 但有一件事 - 如果您指定PID,PID会死亡,然后使用相同的PID生成另一个进程,这不会导致竞争条件吗? (5认同)
  • 关于比赛:等待(2)PID在被等待之前不会被重复使用(它是一个僵尸); 使用bash脚本文档不是很清楚,但似乎(我试过......)shell等待PID并存储返回值供以后使用 - 所以PID可以重用: (5认同)
  • @ Kits89这对我不起作用.根据`wait`手册页,等待多个PID只返回等待的最后一个进程的返回值.所以你需要一个额外的循环并分别等待每个PID,如答案所示. (4认同)
  • 最好使用 `for pid in "${pids[@]}"` 而不是 `for pid in ${pids[*]}`。当然,将“IFS”设置为包含数字的值的人是在自找麻烦,但最好还是编写即使人们“自找麻烦”也能正常工作的代码。:) (3认同)
  • 仅供参考,我找到了一种优雅的方式来做答案:`for $ in $ n_procs; do ./procs[${i}]&; 的PID [$ {I}] = $ !; 完成; 等待$ {pids [*]};` (2认同)
  • 那仍然是越野车。如果重新使用了PID,则wait的退出代码将为非零值,因为您只能等待当前进程的子进程,这将使其看起来像其中一个命令失败,即使它们没有失败。 (2认同)
  • 为什么使用关联数组,为什么不直接使用 pids+=( $! ) (2认同)

Hov*_*ell 273

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
Run Code Online (Sandbox Code Playgroud)

  • `jobs -p`给出处于执行状态的子进程的PID.如果在调用`jobs -p`之前进程完成,它将跳过一个进程.因此,如果任何子进程在`jobs -p`之前结束,那么该进程的退出状态将会丢失. (97认同)
  • 哇,这个答案比最受好评的答案要好.:/ (14认同)
  • @ e40,下面的答案可能更好.甚至更好的方法是使用'(cmd; echo"$?">>"$ tmpfile")运行每个命令,使用此等待,然后读取失败的文件.还注释输出....或者只是在你不在乎的时候使用这个脚本. (3认同)
  • @tkokoszka 准确地说,`jobs -p` 没有给出子进程的 *PIDs*,而是 *GPIDs*。等待逻辑似乎无论如何都可以工作,如果这样的组存在,则它总是在该组上等待,如果不存在则 pid,但是很高兴知道..如果你有 PIDs 或 GPIDs,语法会有所不同.. ie `kill -- -$GPID` vs `kill $PID` (2认同)

Ole*_*nge 47

如果你安装了GNU Parallel,你可以这样做:

# If doCalculations is a function
export -f doCalculations
seq 0 9 | parallel doCalculations {}
Run Code Online (Sandbox Code Playgroud)

GNU Parallel将为您提供退出代码:

  • 0 - 所有作业都运行无误.

  • 1-253 - 部分工作失败.退出状态提供失败作业的数量

  • 254 - 超过253个职位失败.

  • 255 - 其他错误.

观看介绍视频以了解更多信息:http://pi.dk/1

  • @nobar混乱是由于一些包装商为他们的用户搞砸了.如果使用`wget -O - pi.dk/3 |安装 嘘,你不会有任何困惑.如果您的包装商已经为您搞砸了,我建议您向包装商提出问题.应该为GNU Parallel导出变量和函数(export -f)以查看它们(参见`man parallel`:http://www.gnu.org/software/parallel/man.html#aliases_and_functions_do_not_work) (4认同)
  • 这看起来是一个很棒的工具,但我不认为上面的内容在 Bash 脚本中按原样工作,其中 `doCalculations` 是在同一脚本中定义的函数(尽管 OP 不清楚这一要求)。当我尝试时,“parallel”显示“/bin/bash: doCalculations: command not find”(对于上面的“seq 0 9”示例,它显示了 10 次)。请参阅[此处](http://stackoverflow.com/questions/11003418/calling-functions-with-xargs-within-a-bash-script)了解解决方法。 (3认同)
  • 同样有趣的是:`xargs`具有通过`-P`选项并行启动作业的能力.从[here](http://stackoverflow.com/questions/3321738/shell-scripting-using-xargs-to-execute-parallel-instances-of-a-shell-function):`export -f doCalculations; seq 0 9 | xargs -P 0 -n 1 -I {} bash -c"doCalculations {}"`.`xargs`的限制在`parallel`的手册页中列举. (3认同)

ken*_*orb 47

这是一个简单的例子wait.

运行一些流程:

$ sleep 10 &
$ sleep 10 &
$ sleep 20 &
$ sleep 20 &
Run Code Online (Sandbox Code Playgroud)

然后用wait命令等待它们:

$ wait < <(jobs -p)
Run Code Online (Sandbox Code Playgroud)

或者只是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.
Run Code Online (Sandbox Code Playgroud)

  • 这将错过调用 jobs -p 之前失败的作业的退出状态 (3认同)
  • 对于那些刚接触 bash 的人,这里示例中的两个计算是 `sleep 20 &amp;&amp; true` 和 `sleep 20 &amp;&amp; false` —— 即:用你的函数替换它们。要理解`&amp;&amp;`和`||`,运行`man bash`并输入'/'(搜索)然后输入'^ *Lists'(一个正则表达式)然后输入:man将向下滚动到`&amp;&amp;`和`的描述||` (2认同)

pat*_*_ai 41

怎么样简单:

#!/bin/bash

pids=""

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

wait $pids

...code continued here ...
Run Code Online (Sandbox Code Playgroud)

更新:

正如多个评论者指出的那样,上面等待所有进程在继续之前完成,但如果其中一个失败则不会退出和失败,可以使用@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 ...
Run Code Online (Sandbox Code Playgroud)

  • 我对此解决方案有一个明显的担忧:如果给定进程在调用相应的“等待”之前退出,该怎么办?事实证明,这不是问题:如果您对已经退出的进程进行“等待”,则“等待”将立即以已退出进程的状态退出。(谢谢bash的作者!) (4认同)
  • 根据等待手册页,带有多个 PID 的等待仅返回所等待的最后一个进程的返回值。因此,您确实需要一个额外的循环并分别等待每个 PID,如已接受的答案(在评论中)所建议的。 (2认同)
  • 这正是我所需要的,完美地处理任一子流程中的失败并确保主流程完成(如果任一子流程失败则提前完成,或者继续执行“...代码在此继续...”,如果全部)仅当所有子流程完成后,子流程才会成功。 (2认同)

Mar*_*gar 38

这是我到目前为止所提出的.我想看看如果一个孩子终止,如何中断睡眠命令,这样就不必调整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
Run Code Online (Sandbox Code Playgroud)


nob*_*bar 19

为了并行化......

for i in $(whatever_list) ; do
   do_something $i
done
Run Code Online (Sandbox Code Playgroud)

把它翻译成这个......

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 {}
      }' 
   )
Run Code Online (Sandbox Code Playgroud)
  • 如果在一个进程中发生错误,它将不会中断其他进程,但它将导致整个序列的非零退出代码.
  • 在任何特定情况下,导出函数和变量可能是必需的,也可能不是必需的.
  • 您可以--max-procs根据所需的并行度进行设置(0意思是"一次性").
  • GNU Parallel在使用时提供了一些附加功能xargs- 但默认情况下并不总是安装.
  • for在这个例子中,循环并不是绝对必要的,因为echo $i它基本上只是重新生成输出$(whatever_list.我只是认为使用for关键字可以更容易地看到发生了什么.
  • Bash字符串处理可能会令人困惑 - 我发现使用单引号最适合包装非平凡的脚本.
  • 您可以轻松地中断整个操作(使用^ C或类似操作),这与更直接的Bash并行方法不同.

这是一个简化的工作示例......

for i in {0..5} ; do echo $i ; done |xargs -I{} --max-procs 2 bash -c '
   {
   echo sleep {}
   sleep 2s
   }'
Run Code Online (Sandbox Code Playgroud)


Gab*_*les 12

这是@Luca Tettamanti 得到最多支持的答案的扩展,以制作一个完全可运行的示例。

这个答案让我想知道

变量是什么类型n_procs,它包含什么?变量是什么类型procs,它包含什么?有人可以通过添加这些变量的定义来更新此答案以使其可运行吗?我不明白怎么办。

...并且:

  • 当子进程完成时,如何从子进程中获取返回码(这是这个问题的关键)?

无论如何,我已经弄清楚了,所以这是一个完全可运行的示例。

笔记:

  1. $!就是如何获取最后执行的子进程的PID(进程ID)
  2. &运行任何带有其后的命令cmd &(例如 ),都会使其作为与主进程并行的子进程在后台运行。
  3. myarray=()是如何在 bash 中创建数组。
  4. wait要了解有关内置命令的更多信息,请参阅help wait。另请参阅有关 Job Control 内置程序的官方 Bash 用户手册wait,例如和jobs,此处: https: //www.gnu.org/software/bash/manual/html_node/Job-Control-Builtins.html#索引等待

完整的、可运行的程序:等待所有进程结束

multi_process_program.sh(来自我的eRCaGuy_hello_world存储库):

#!/usr/bin/env bash


# This is a special sleep function which returns the number of seconds slept as
# the "error code" or return code" so that we can easily see that we are in
# fact actually obtaining the return code of each process as it finishes.
my_sleep() {
    seconds_to_sleep="$1"
    sleep "$seconds_to_sleep"
    return "$seconds_to_sleep"
}

# Create an array of whatever commands you want to run as subprocesses
procs=()  # bash array
procs+=("my_sleep 5")
procs+=("my_sleep 2")
procs+=("my_sleep 3")
procs+=("my_sleep 4")

num_procs=${#procs[@]}  # number of processes
echo "num_procs = $num_procs"

# run commands as subprocesses and store pids in an array
pids=()  # bash array
for (( i=0; i<"$num_procs"; i++ )); do
    echo "cmd = ${procs[$i]}"
    ${procs[$i]} &  # run the cmd as a subprocess
    # store pid of last subprocess started; see:
    # https://unix.stackexchange.com/a/30371/114401
    pids+=("$!")
    echo "    pid = ${pids[$i]}"
done

# OPTION 1 (comment this option out if using Option 2 below): wait for all pids
for pid in "${pids[@]}"; do
    wait "$pid"
    return_code="$?"
    echo "PID = $pid; return_code = $return_code"
done
echo "All $num_procs processes have ended."
Run Code Online (Sandbox Code Playgroud)

通过运行将上面的文件更改为可执行文件chmod +x multi_process_program.sh,然后像这样运行它:

time ./multi_process_program.sh 
Run Code Online (Sandbox Code Playgroud)

样本输出。查看调用中命令的输出如何time显示运行时间为 5.084 秒。我们还能够成功地从每个子流程检索返回代码。

eRCaGuy_hello_world/bash$ time ./multi_process_program.sh 
num_procs = 4
cmd = my_sleep 5
    pid = 21694
cmd = my_sleep 2
    pid = 21695
cmd = my_sleep 3
    pid = 21697
cmd = my_sleep 4
    pid = 21699
PID = 21694; return_code = 5
PID = 21695; return_code = 2
PID = 21697; return_code = 3
PID = 21699; return_code = 4
All 4 processes have ended.
PID 21694 is done; return_code = 5; 3 PIDs remaining.
PID 21695 is done; return_code = 2; 2 PIDs remaining.
PID 21697 is done; return_code = 3; 1 PIDs remaining.
PID 21699 is done; return_code = 4; 0 PIDs remaining.

real    0m5.084s
user    0m0.025s
sys 0m0.061s
Run Code Online (Sandbox Code Playgroud)

更进一步:确定每个进程结束时的实时状态

如果您想在每个进程完成时执行某些操作,并且不知道它们何时完成,则可以在无限循环中轮询while以查看每个进程何时终止,然后执行您想要的任何操作。

只需注释掉上面的“OPTION 1”代码块,并将其替换为“OPTION 2”块:

eRCaGuy_hello_world/bash$ time ./multi_process_program.sh 
num_procs = 4
cmd = my_sleep 5
    pid = 21694
cmd = my_sleep 2
    pid = 21695
cmd = my_sleep 3
    pid = 21697
cmd = my_sleep 4
    pid = 21699
PID = 21694; return_code = 5
PID = 21695; return_code = 2
PID = 21697; return_code = 3
PID = 21699; return_code = 4
All 4 processes have ended.
PID 21694 is done; return_code = 5; 3 PIDs remaining.
PID 21695 is done; return_code = 2; 2 PIDs remaining.
PID 21697 is done; return_code = 3; 1 PIDs remaining.
PID 21699 is done; return_code = 4; 0 PIDs remaining.

real    0m5.084s
user    0m0.025s
sys 0m0.061s
Run Code Online (Sandbox Code Playgroud)

注释掉选项 1 并使用选项 2 的完整程序的示例运行和输出:

eRCaGuy_hello_world/bash$ ./multi_process_program.sh 
num_procs = 4
cmd = my_sleep 5
    pid = 22275
cmd = my_sleep 2
    pid = 22276
cmd = my_sleep 3
    pid = 22277
cmd = my_sleep 4
    pid = 22280
PID 22276 is done; return_code = 2; 3 PIDs remaining.
PID 22277 is done; return_code = 3; 2 PIDs remaining.
PID 22280 is done; return_code = 4; 1 PIDs remaining.
PID 22275 is done; return_code = 5; 0 PIDs remaining.
Run Code Online (Sandbox Code Playgroud)

PID XXXXX is done该进程终止后,每一行都会立即打印出来!请注意,即使sleep 5(在本例中为 PID 22275)的进程首先运行,但它最后完成,并且我们在每个进程终止后立即成功检测到它。我们还成功检测到每个返回代码,就像选项 1 中一样。

其他参考资料:

  1. *****+ [非常有帮助]获取后台进程的退出代码- 这个答案教会了我关键原则(强调):

    wait <n>等待具有 PID 的进程完成(它将阻塞直到进程完成,因此在确定进程完成之前您可能不想调用此函数),然后返回已完成进程的退出代码。

    换句话说,它帮助我知道即使在该过程完成后,您仍然可以调用wait它来获取其返回码!

  2. 如何检查进程 ID (PID) 是否存在

    1. 我的答案
  3. 从 Bash 数组中删除元素- 请注意,bash 数组中的元素实际上并未被删除,它们只是“未设置”。请参阅上面代码中我的注释,了解这意味着什么。

  4. 如何使用命令行可执行文件true在 bash 中创建无限 while 循环:https://www.cyberciti.biz/faq/bash-infinite-loop/


小智 11

这是我使用的东西:

#wait for jobs
for job in `jobs -p`; do wait ${job}; done
Run Code Online (Sandbox Code Playgroud)


Aln*_*tak 7

我不相信Bash的内置功能是可能的.

可以在孩子退出时收到通知:

#!/bin/sh
set -o monitor        # enable script job control
trap 'echo "child died"' CHLD
Run Code Online (Sandbox Code Playgroud)

但是,没有明显的方法可以让孩子在信号处理程序中退出状态.

获取子级状态通常wait是较低级别POSIX API中的函数族的工作.不幸的是,Bash对此的支持是有限的 - 您可以等待一个特定的子进程(并获得其退出状态),或者您可以等待所有这些进程,并始终获得0结果.

看起来不可能做的是相当于waitpid(-1),阻塞直到任何子进程返回.


Jas*_*ski 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
Run Code Online (Sandbox Code Playgroud)

我使用非常类似于并行启动/停止服务器/服务的东西并检查每个退出状态.对我来说很棒.希望这可以帮助别人!

  • @karsten - 这是一个不同的问题。假设您使用的是 bash,您可以捕获退出条件(包括 Ctrl+C),并使用 `trap "kill 0" EXIT` 杀死当前进程和所有子进程 (2认同)

ste*_*nct 6

如果您有 bash 4.2 或更高版本可用,以下内容可能对您有用。它使用关联数组来存储任务名称及其“代码”以及任务名称及其 pid。我还内置了一个简单的速率限制方法,如果您的任务消耗大量 CPU 或 I/O 时间并且您想限制并发任务的数量,它可能会派上用场。

该脚本在第一个循环中启动所有任务并在第二个循环中使用结果。

对于简单的情况,这有点矫枉过正,但它允许非常整洁的东西。例如,可以将每个任务的错误消息存储在另一个关联数组中,并在一切稳定后打印它们。

#! /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
Run Code Online (Sandbox Code Playgroud)


Jay*_*yen 6

#!/bin/bash
set -m
for i in `seq 0 9`; do
  doCalculations $i &
done
while fg; do true; done
Run Code Online (Sandbox Code Playgroud)
  • set -m允许您在脚本中使用 fg 和 bg
  • fg,除了将最后一个进程置于前台外,还具有与其前台进程相同的退出状态
  • while fgfg当任何退出状态非零时将停止循环

不幸的是,当后台进程以非零退出状态退出时,这不会处理这种情况。(循环不会立即终止。它将等待前面的进程完成。)


err*_*rrr 5

如果任何doCalculations失败,以下代码将等待所有计算的完成并返回退出状态1 .

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


est*_*ani 5

只需将结果存储在shell中,例如存储在文件中.

#!/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
Run Code Online (Sandbox Code Playgroud)


小智 5

我已经尝试过并结合了此处其他示例中的所有最佳部分。该脚本将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
Run Code Online (Sandbox Code Playgroud)


Ors*_*ong 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
}
Run Code Online (Sandbox Code Playgroud)

例如,等待所有三个进程完成,如果执行时间超过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
Run Code Online (Sandbox Code Playgroud)


Eri*_*sty 5

等待所有作业并返回最后一个失败作业的退出代码。与上述解决方案不同,这不需要保存 pid 或修改脚本的内部循环。只是离开,然后等待。

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

编辑:修复了可能被运行不存在命令的脚本所愚弄的错误。

  • 这将起作用并可靠地给出执行命令中的第一个错误代码,除非它恰好是“未找到命令”(代码 127)。 (2认同)