'How to wait in bash script to subprocess, if one of them failed so stop everyone

How to wait in bash script to subprocess and if one of them return exit code 1 so I want to stop all subprocess.

This is what I tried to do. But there are a some of issues:

  1. If the first process is longer than all the others, and another process fails in the background ... then the script waits for the first process to finish, even though another process has already failed.

  2. Can't detect that doSomething failed because I use pipe for the desired print format.

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

Anyone have an idea?



Solution 1:[1]

The gist of it would be:

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

The idea is to make the sub-processes notify the parent script when they fail (with a SIGUSR1 signal); the main script will then kill all the sub-processes when it receives that signal.
There's a problem though: killing the PID of a sub-process might not be enough, for example when it is currently running a command with a |. In those cases you need to kill the whole process group, which can be done by enabling job control with set -m and by using a negative PID in the kill command.

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1