【问题标题】: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?
【发布时间】:2010-09-26 05:55:18
【问题描述】:

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

简单脚本:

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

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

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

【问题讨论】:

  • 这可以显着改进以触及 wait -n,在现代 bash 中可用,仅在第一个/下一个命令完成时返回。
  • 如果你想使用 Bash 进行测试,试试这个:github.com/sstephenson/bats
  • 积极开发BATS已移至github.com/bats-core/bats-core
  • @CharlesDuffy wait -n 有一个小问题:如果没有剩余的子作业(也称为竞争条件),它会返回一个非零退出状态(失败),这与失败的子作业无法区分过程。
  • @drevicko:在这里等待 -n 解决方案:stackoverflow.com/a/59723887/627042

标签: bash process wait


【解决方案1】:

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

【讨论】:

  • Weel,因为您将等待所有进程,例如您正在等待第一个,而第二个已经完成(无论如何,第二个将在下一次迭代中被选中)。这与您在 C 中使用 wait(2) 的方法相同。
  • 啊,我明白了 - 不同的解释 :) 我把这个问题理解为“当任何子进程退出时立即返回退出代码 1”。
  • PID 确实可以重用,但你不能等待不是当前进程的子进程(在这种情况下等待失败)。
  • 您也可以使用 %n 来指代第 n:th 个后台作业,使用 %% 来指代最近的作业。
  • @Nils_M:你是对的,我很抱歉。所以它会是这样的:for i in $n_procs; do ./procs[${i}] & ; pids[${i}]=$!; done; for pid in ${pids[*]}; do wait $pid; done;,对吧?
【解决方案2】:

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

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

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

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

获取子状态通常是较低级别 POSIX API 中 wait 系列函数的工作。不幸的是,Bash 对此的支持是有限的——你可以等待 一个 特定的子进程(并获得它的退出状态),或者你可以等待 all 他们,并且总是得到一个0 个结果。

看起来不可能做的是等同于waitpid(-1),它会阻塞直到任何子进程返回。

【讨论】:

    【解决方案3】:

    我想也许在发送到后台的 subshel​​l 中运行 doCalculations; echo "$?" >>/tmp/acc,然后 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
    

    【讨论】:

    • 多个appender应该有任何问题,虽然返回值可能被乱序写入,所以你不知道哪个进程返回了什么......
    • 您可以只发送带有状态的标识信息。无论如何,OP 只想知道 任何 子进程是否返回 status ≠ 0,而不考虑具体是哪些子进程。
    • 得到 20 个结果而不是 21 个做 for i in $( seq 1 20 )
    【解决方案4】:

    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
    

    【讨论】:

    • jobs -p 正在提供处于执行状态的子进程的 PID。如果进程在调用jobs -p 之前完成,它将跳过一个进程。因此,如果任何子进程在jobs -p 之前结束,则该进程的退出状态将丢失。
    • 哇,这个答案比评分最高的答案要好得多。 ://
    • @e40 下面的答案可能更好。甚至更好的方法可能是使用 '(cmd; echo "$?" >> "$tmpfile") 运行每个命令,使用此等待,然后读取失败的文件。也注释输出。 …或者当你不太在意时使用这个脚本。
    • @tkokoszka 准确地说是jobs -p 没有给出子进程的PIDs,而是GPIDs。等待逻辑似乎仍然有效,如果存在这样的组,它总是等待该组,如果不存在,它总是等待 pid,但是很高兴知道.. 特别是如果要在此基础上构建并合并诸如向子进程发送消息之类的东西如果您有 PID 或 GPID,语法会有所不同。即 kill -- -$GPIDkill $PID
    • 听起来就像这个答案一样简单,对吧?错误的!如果你把那些sleeper 的东西放在forwhile 循环上,它就会变成子shell。而jobswait 不考虑子shell 的后台作业。所以,这就是为什么我们应该使用公认的答案,即使它看起来很复杂。
    【解决方案5】:

    这是我到目前为止的想法。我想看看如果孩子终止,如何中断睡眠命令,这样就不必调整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 或将其设置得很低,因为循环内没有启动任何进程,我认为它不会太昂贵。
    【解决方案6】:

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

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

    【讨论】:

    • 我认为您需要的只是(doCalculations $i; echo $?) & ... bg & wait 在这里是多余的。巧妙地使用 grep!
    【解决方案7】:

    如果您安装了 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

    【讨论】:

    • 谢谢!但是你忘了提到我后来陷入的“混乱”问题:unix.stackexchange.com/a/35953
    • 这看起来是一个很棒的工具,但我不认为上面的工作在 Bash 脚本中按原样工作,其中 doCalculations 是在同一个脚本中定义的函数(尽管 OP 不是明确这个要求)。当我尝试时,parallel 会说 /bin/bash: doCalculations: command not found(对于上面的 seq 0 9 示例,它会说 10 次)。有关解决方法,请参阅 here
    • 还有兴趣:xargs 具有通过-P 选项并行启动作业的能力。来自hereexport -f doCalculations ; seq 0 9 |xargs -P 0 -n 1 -I{} bash -c "doCalculations {}"xargs 的限制在 parallel 的手册页中列举。
    • 如果doCalculations 依赖于任何其他脚本内部环境变量(自定义PATH 等),它们可能需要在启动parallel 之前显式exported。跨度>
    • @nobar 混乱是由于一些打包者为他们的用户搞砸了。如果您使用wget -O - pi.dk/3 | sh 安装,您将不会感到困惑。如果您的包装商为您搞砸了,我鼓励您向您的包装商提出问题。应该为 GNU Parallel 导出变量和函数(export -f)以查看它们(参见man parallel:gnu.org/software/parallel/…
    【解决方案8】:

    只需将结果存储在外壳之外,例如在一个文件中。

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

    【讨论】:

      【解决方案9】:

      要并行化这个...

      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-procs0 表示“一次性”)。
      • GNU Parallel 用于替代 xargs 时提供了一些附加功能——但并非总是默认安装。
      • for 循环在此示例中并非绝对必要,因为echo $i 基本上只是重新生成$(whatever_list 的输出)。我只是认为使用 for 关键字可以更轻松地了解发生了什么。
      • Bash 字符串处理可能会令人困惑——我发现使用单引号最适合包装重要的脚本。
      • 您可以轻松中断整个操作(使用 ^C 或类似方法)unlike the the more direct approach to Bash parallelism

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

      for i in {0..5} ; do echo $i ; done |xargs -I{} --max-procs 2 bash -c '
         {
         echo sleep {}
         sleep 2s
         }'
      
      【解决方案10】:

      如果您有 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
      

      【讨论】:

        【解决方案11】:

        我最近用过这个(感谢 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
        

        从那里可以轻松推断,并有一个触发器(触摸文件,发送信号)并更改计数标准(计数文件触摸,或其他)以响应该触发器。或者,如果您只想要“任何”非零 rc,只需从 save_status 中终止锁。

        【讨论】:

        • 现在如果我们能把它减少到 1 行......
        【解决方案12】:

        我需要这个,但目标进程不是当前 shell 的子进程,在这种情况下 wait $PID 不起作用。我确实找到了以下替代方案:

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

        这取决于 procfs 的存在,它可能不可用(例如,Mac 不提供)。所以为了便携性,你可以改用这个:

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

        【讨论】:

          【解决方案13】:
          #!/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 将停止循环

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

          【讨论】:

            【解决方案14】:

            我对此进行了尝试,并结合了此处其他示例中的所有最佳部分。当任何后台进程退出时,该脚本将执行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
            

            【讨论】:

              【解决方案15】:

              简单地说:

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

              【讨论】:

              • 根据等待手册页,多个PID的等待只返回最后等待的进程的返回值。因此,您确实需要一个额外的循环并分别等待每个 PID,如已接受的答案(以 cmets 为单位)中所建议的那样。
              • 因为这个页面的其他任何地方似乎都没有说明,所以我会补充说循环是for pid in $pids; do wait $pid; done
              • @bisounours_tronconneuse 是的,你知道。见help wait - 有多个ID wait 只返回最后一个的退出代码,正如@vlad-frolov 上面所说的那样。
              • 我对这个解决方案有一个明显的担忧:如果给定进程在调用相应的wait 之前退出怎么办?事实证明这不是问题:如果您在已经退出的进程上waitwait 将立即以已退出进程的状态退出。 (谢谢bash作者!)
              • 这正是我所需要的,完美地处理任一子进程中的故障并确保主进程完成(如果任一子进程失败,则尽早完成,或者继续 ...code continued here... 如果全部子流程成功)只有在所有子流程都完成后。
              【解决方案16】:

              我刚刚将脚本修改为后台并并行化进程。

              我做了一些实验(在带有 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 文件,同时也避免在子 shell 中运行“等待”,这根本不起作用。

              【讨论】:

                【解决方案17】:

                捕获 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 !!!
                

                【讨论】:

                  【解决方案18】:

                  trap 是你的朋友。您可以在许多系统中捕获 ERR。您可以在每个命令后捕获 EXIT,或在 DEBUG 上执行一段代码。

                  这是所有标准信号之外的。

                  编辑

                  这是一个错误帐户的意外登录,所以我没有看到示例请求。

                  在我的常规帐户上试试这里。

                  Handle exceptions in bash scripts

                  【讨论】:

                  • 请你用一些例子详细说明你的答案。
                  【解决方案19】:

                  我看到这里列出了很多很好的例子,也想把我的也扔进去。

                  #! /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+C 停止它时,我仍然看到进程在后台运行。
                  • @karsten - 这是一个不同的问题。假设您使用的是 bash,您可以捕获退出条件(包括 Ctrl+C)并使用 trap "kill 0" EXIT 杀死当前和所有子进程
                  • @Phil 是正确的。由于这些是后台进程,杀死父进程只会让所有子进程继续运行。我的示例没有捕获任何信号,如 Phil 所述,如有必要,可以添加。
                  【解决方案20】:

                  这是使用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.
                  

                  【讨论】:

                  • 对于那些不熟悉 bash 的人来说,这里的示例中的两个计算是 sleep 20 &amp;&amp; truesleep 20 &amp;&amp; false -- 即:用你的函数替换它们。要了解&amp;&amp;||,运行man bash 并输入'/'(搜索)然后输入'^ *Lists'(正则表达式)然后输入:man 将向下滚动到&amp;&amp;|| 的描述
                  • 您可能应该检查文件“失败”在开始时不存在(或删除它)。根据应用程序,在 || 之前添加 '2>&1' 以捕获 STDERR 失败也是一个好主意。
                  • 我喜欢这个,有什么缺点吗?实际上,只有当我想列出所有子流程并采取一些行动时,例如。发送信号,我将尝试记账 pid 或迭代作业。等待完成,只需wait
                  • 这将错过调用jobs -p之前失败的作业的退出状态
                  • 不知道为什么,但wait &lt; &lt;(jobs -p) 行给了我一个语法错误
                  【解决方案21】:
                  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

                  【讨论】:

                    【解决方案22】:

                    这是我的版本,适用于多个 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 be replaced by yours
                    function Logger {
                        local value="${1}"
                    
                        echo $value
                    }
                    

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

                    【讨论】:

                      【解决方案23】:

                      这里已经有很多答案了,但我很惊讶似乎没有人建议使用数组......所以这就是我所做的 - 这可能对将来的某些人有用。

                      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
                      

                      【讨论】:

                        【解决方案24】:

                        这很有效,应该和@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-subshell

                        【讨论】:

                        • trap $? $$ 似乎每次都将退出代码设置为 0 并将 PID 设置为当前正在运行的 bash shell
                        • 你绝对确定?不确定这是否有意义。
                        【解决方案25】:

                        可能存在在等待进程之前进程已完成的情况。如果我们触发等待一个已经完成的进程,它将触发一个错误,比如 pid is not a child of this shell。为了避免这种情况,可以使用以下函数来判断进程是否完成:

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

                        【讨论】:

                          【解决方案26】:

                          我认为并行运行作业和检查状态的最直接方法是使用临时文件。已经有几个类似的答案(例如 Nietzche-jou ​​和 mug896)。

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

                          上面的代码不是线程安全的。如果您担心上面的代码会与其自身同时运行,最好使用更独特的文件名,例如 fail.$$。最后一行是为了满足要求:“当任何子进程以代码结束时返回退出代码 1 != 0?”我在那里提出了一个额外的要求来清理。写成这样可能更清楚:

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

                          这是一个类似的 sn-p 用于从多个作业收集结果:我创建一个临时目录,将所有子任务的输出记录在一个单独的文件中,然后将它们转储以供审查。这与问题不符 - 我将其作为奖励:

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

                          【讨论】:

                            【解决方案27】:

                            等待多个子进程并在其中任何一个以非零状态码退出时退出的解决方案是使用'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”用于不存在的进程,这意味着子进程可能已退出。

                            【讨论】:

                              【解决方案28】:

                              这是我使用的东西:

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

                              【讨论】:

                                【解决方案29】:

                                正是为了这个目的,我编写了一个名为:forbash 函数。

                                注意: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
                                

                                参考文献

                                【讨论】:

                                  【解决方案30】:

                                  等待所有作业并返回最后一个失败作业的退出代码。与上述解决方案不同,这不需要 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
                                  }
                                  

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

                                  【讨论】:

                                  • 这将有效并可靠地从您执行的命令中给出第一个错误代码,除非它碰巧是“找不到命令”(代码 127)。
                                  • -n 标志将等待下一个孩子改变状态并返回代码。我不确定如果两个几乎完全相同的时间完成会发生什么?无论如何,这对于我的用例来说应该足够了,谢谢!
                                  猜你喜欢
                                  • 1970-01-01
                                  • 2017-01-19
                                  • 2015-05-23
                                  • 1970-01-01
                                  • 2018-05-29
                                  • 2021-02-20
                                  • 1970-01-01
                                  相关资源
                                  最近更新 更多