shell 如何在bash脚本中等待子进程,如果其中一个失败,则停止所有子进程

xe55xuns  于 2022-12-19  发布在  Shell
关注(0)|答案(2)|浏览(233)

如何在bash脚本中等待子进程,如果其中一个返回退出代码1,那么我想停止所有子进程。
这就是我想做的。但是有一些问题:
1.如果第一个进程比所有其他进程都长,并且另一个进程在后台失败...则脚本将等待第一个进程完成,即使另一个进程已经失败。
1.无法检测到doSomething失败,因为我使用管道作为所需的打印格式。

#!/bin/bash
    
    function doSomething()
    {
            echo [ $1 start ]
    
            sleep $1
    
            if [ $1 == 10 ]; then
                    failed
            fi
    
            echo [ sleep $1 ]: done
    }
    
    function failed(){
                    sleep 2
                    echo ------ process failed ------
                    exit 1
    }
    
    function process_log() {
            local NAME=$1
            while read Line; do
                    echo [Name ${NAME}]: ${Line}
            done
    }
    
    pids=""
    
    
    (doSomething 4 | process_log 4)&
    pids+="$! "
    
    (doSomething 17 | process_log 17)&
    pids+="$! "
    
    (doSomething 6 | process_log 6)&
    pids+="$! "
    
    (doSomething 10 | process_log 10)&
    pids+="$! "
    
    (doSomething 22 | process_log 22)&
    pids+="$! "
    
    (doSomething 5 | process_log 5)&
    pids+="$! "
    
    
    for pid in $pids; do
           wait $pid || (pkill -P $$ ; break)
    done
    
    echo done program

有人有主意吗?

7z5jn7bk

7z5jn7bk1#

它的要点是:

#!/bin/bash
set -m # needed for using negative PIDs
trap '{ kill -- $(jobs -rp | sed s/^/-/); wait; } 2> /dev/null' USR1

doSomething() {
    echo "[ $1 start ]"
    sleep "$1"
    [[ $1 == 10 ]] && failed
    echo "[ sleep $1 ]: done"
}

failed(){
    echo "------ process failed ------" 1>&2
    kill -USR1 "$$"
}

process_log() {
    local name="$1" line
    while IFS='' read -r line; do
        echo "[Name $name]: $line"
    done
}

{ doSomething  4 | process_log  4; } &
{ doSomething 17 | process_log 17; } &
{ doSomething  6 | process_log  6; } &
{ doSomething 10 | process_log 10; } &
{ doSomething 22 | process_log 22; } &
{ doSomething  5 | process_log  5; } &

wait

echo "done program"
[Name 4]: [ 4 start ]
[Name 6]: [ 6 start ]
[Name 17]: [ 17 start ]
[Name 5]: [ 5 start ]
[Name 10]: [ 10 start ]
[Name 22]: [ 22 start ]
[Name 4]: [ sleep 4 ]: done
[Name 5]: [ sleep 5 ]: done
[Name 6]: [ sleep 6 ]: done
------ process failed ------
[Name 10]: [ sleep 10 ]: done
done program
解释

其思想是让子进程在失败时通知父脚本(使用SIGUSR1信号);主脚本接着将在其接收到所述信号时杀死所有子进程。
但有一个问题:删除子进程的PID可能还不够,例如,当它当前正在运行带有|的命令时。在这些情况下,您需要删除整个 * 进程组 *,这可以通过使用set -m启用 * 作业控制 * 并在kill命令中使用负PID来完成。

zbdgwd5y

zbdgwd5y2#

    • GNU并行--halt-on-error now,1 --tag**

对于在每一行前面附加参数的特定process_log,GNU parallel可以用一行代码来完成。
安装:

sudo apt install parallel

试验:

myfunc() {
    echo "start: $1"
    i=0
    while [ $i -lt $1 ]; do
      echo "$((i * $1))"
      sleep 1
      i=$((i + 1))
    done
    [[ $1 == 3 ]] && exit 1
    echo "end: $1"
}
export -f myfunc
parallel --lb --halt-on-error now,fail=1 --tag myfunc ::: 1 2 3 4 5

输出:

4       start: 4
4       0
3       start: 3
3       0
1       start: 1
1       0
2       start: 2
2       0
5       start: 5
5       0
1       end: 1
4       4
3       3
2       2
5       5
2       end: 2
4       8
3       6
5       10
parallel: This job failed:
myfunc 3

我们看到45没有完成,因为3在它们之前失败了,而且每一行都有输入参数作为前缀。
GNU parallel还可以涵盖其他一些常见的前缀用例:

  • 经过时间:在bash脚本输出的每一行前面加上自脚本启动以来的时间
  • datetime: https://superuser.com/questions/1574769/parallel-current-datetime-in-tagstring/1601670#1601670

我的补充意见来自:如果任何函数并行失败,则停止bash也适用于此处。
在Ubuntu 22.04上测试。

相关问题