Search code examples
bashparallel-processingbackground-processexit-code

Get exit status of background processes in bash


I have tried multiple different ways to retrieve the exit status of a background process:

  1. Capture pid of each background process, store in an array and then wait for each PID, get return status of each PID and store in a STATUS array.

Con: pid is not a child of this shell

  1. tail --pid= -f /dev/null

Con: exit status is always 0 here

Looked around for various answers on stackoverflow. I am still not able to get it working. Can you please help and let me know where am I going wrong?

PIDS=()
STATUS=()
OVERALL_EXIT=0

# run processes and store pids in array

for target in ${target_list} ; do
    ./<script_to_execute> ${target} &
    PIDS+=$!
done


# wait for all processes to finish and then capture return status of each
for pid in ${PIDS[@]}; do
    echo "${pid}"
    wait ${pid} 
    #tail —pid=${pid} -f /dev/null
    #ps ax | grep ${pid} | grep -v grep 
    STATUS+=($?)
done

# looping through the status arr to check exit code for each
i=0
for st in ${STATUS[@]}; do
    if [[ ${st} -ne 0 ]]; then
        echo "$i failed"
        OVERALL_EXIT=1
    else
        echo "$i finished"
    fi
    ((i+=1))
done

exit ${overall_exit}

Solution

  • PIDS+=$!
    

    ...doesn't do what you think it does. Consider:

    PIDS=( )
    PIDS+=11
    PIDS+=22
    PIDS+=33
    declare -p PIDS
    

    ...if what you expect this to output is:

    declare -a PIDS='([0]="11" [1]="22" [2]="33")
    

    ...you'd be mistaken, because what it actually emits is:

    declare -a PIDS='([0]="112233")'
    

    ...because += only appends a new array element when the thing on the right-hand side is an array.

    Thus, you get a not a child of this shell error because the result of concatenating all your PIDs together into a single string isn't a PID that actually exists.

    To fix it, use parens: PIDS+=( "$!" )


    To provide an end-to-end example:

    #!/usr/bin/env bash
    
    # run four different processes; two exit status 0, one exits status 1, on exits status 2
    # ...exits happen at delays ranging between 2-5 seconds.
    delays=( 5 3 2 4 )
    exits=(  0 0 1 2 )
    for idx in "${!delays[@]}"; do
      (sleep "${delays[$idx]}"; exit "${exits[$idx]}") &
      pids+=( "$!" )
    done
    
    exit_status=0
    for pid in "${pids[@]}"; do
      wait "$pid"; (( exit_status |= $? ))
    done
    echo "Combined exit status is $exit_status"
    exit "$exit_status"
    

    ...properly exits after 5 seconds with:

    Combined exit status is 3