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

后端 未结 30 2478
悲哀的现实
悲哀的现实 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:10

    Exactly for this purpose I wrote a bash function called :for.

    Note: :for not only preserves and returns the exit code of the failing function, but also terminates all parallel running instance. Which might not be needed in this case.

    #!/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 $?
    }
    

    usage

    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
    

    References

    • [1]: blog
    • [2]: gist

提交回复
热议问题