How to wait in bash for several subprocesses to finish and return exit code !=0 when any subprocess ends with code !=0?

后端 未结 30 2412
悲哀的现实
悲哀的现实 2020-11-22 03:50

How to wait in a bash script for several subprocesses spawned from that script to finish and return exit code !=0 when any of the subprocesses ends with code !=0 ?

S

相关标签:
30条回答
  • 2020-11-22 04:32

    trap is your friend. You can trap on ERR in a lot of systems. You can trap EXIT, or on DEBUG to perform a piece of code after every command.

    This in addition to all the standard signals.

    0 讨论(0)
  • 2020-11-22 04:33

    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
    
    0 讨论(0)
  • 2020-11-22 04:33

    Here's my version that works for multiple pids, logs warnings if execution takes too long, and stops the subprocesses if execution takes longer than a given value.

    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
    }
    

    Example, wait for all three processes to finish, log a warning if execution takes loger than 5 seconds, stop all processes if execution takes longer than 120 seconds. Don't exit program on failures.

    function something {
    
        sleep 10 &
        pids="$!"
        sleep 12 &
        pids="$pids;$!"
        sleep 9 &
        pids="$pids;$!"
    
        WaitForTaskCompletion $pids 5 120 ${FUNCNAME[0]} false
    }
    # Launch the function
    someting
        
    
    0 讨论(0)
  • 2020-11-22 04:33

    This works, should be just as a good if not better than @HoverHell's answer!

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

    and of course, I have immortalized this script, in an NPM project which allows you to run bash commands in parallel, useful for testing:

    https://github.com/ORESoftware/generic-subshell

    0 讨论(0)
  • 2020-11-22 04:33

    Wait for all jobs and return the exit code of the last failing job. Unlike solutions above, this does not require pid saving. Just bg away, and wait.

    function wait_ex {
        # this waits for all jobs and returns the exit code of the last failing job
        ecode=0
        while true; do
            wait -n
            err="$?"
            [ "$err" == "127" ] && break
            [ "$err" != "0" ] && ecode="$err"
        done
        return $ecode
    }
    
    0 讨论(0)
  • 2020-11-22 04:35

    Trapping CHLD signal may not work because you can lose some signals if they arrived simultaneously.

    #!/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 !!!
    
    0 讨论(0)
提交回复
热议问题