How to handle wrong exit code upon child failure with SIGCHLD

Viewed 376

I have a shell script which makes a bunch of child process to run in the background. If any of those fail I want to terminate all child process and the parent process with kill -- -$$.

I attempted to make a simpler signal handler function check_exit_code() in the parent process to see if it would work fine. It is called every time a child process terminates, using trap and SIGCHLD signal.

#!/bin/sh
set -o monitor

function check_exit_code {
    if [ $1 -eq "0" ]
    then 
        echo "Success: $1"
    else 
        echo "Fail: $1"
    fi
}

trap "check_exit_code $?" SIGCHLD
mycommand1 &
mycommand2 &
mycommand3 &
...
wait

Unfortunately, this only returns Success: 0 even when mycommand# failed and its exit code was 2, so I changed the function to the following.

#!/bin/sh
set -o monitor

function check_exit_code() {
    local EXIT_STATUS=$? 
    if [ "$EXIT_STATUS" -eq "0" ]
    then 
        echo "Success: $EXIT_STATUS"
    else 
        echo "Fail: $EXIT_STATUS"
    fi
}

trap "check_exit_code" SIGCHLD
mycommand1 &
mycommand2 &
mycommand3 &
...
wait

This only returns Fail: 145, which mycommand# cannot return. I suspect that when I write $? I receive the exit status of another command. What is the problem? How would you fix it?

1 Answers

The problem with your first attempt is the double quotes around check_exit_status $?. The shell expands $? to zero before setting the trap and thus, a SIGCHLD triggers check_exit_status 0 no matter what, hence the constant Success: 0 output.

Regarding your second attempt, upon entry to the trap action, the special parameter ? holds the most recent pipeline's exit status as usual, which is in this case wait. Since a trap was set for SIGCHLD, the shell interrupts wait and assigns 128 + 17 (numeric equivalent of SIGCHLD) to ? upon receiving that signal.


With bash-5.1.4 or a newer version, you can achieve the desired result like so:

while true; do
  wait -n -p pid
  case $?,$pid in
  ( 0* ) # a job has exited with zero
    continue ;;
  ( *, ) # pid is empty, no jobs left
    break ;;
  ( *  ) # a job has exited with non-zero
    trap "exit $?" TERM
    kill 0
  esac
done
Related