Search code examples
bashsignalsexit-code

Identify whether a process was killed by a signal in bash


Consider these two C programs:

#include <signal.h>

int main(void) {
    raise(SIGTERM);
}
int main(void) {
    return 143;
}

If I run either one, the value of $? in bash will be 143. The wait syscall lets you distinguish them, though:

wait4(-1, [{WIFSIGNALED(s) && WTERMSIG(s) == SIGTERM}], 0, NULL) = 11148
wait4(-1, [{WIFEXITED(s) && WEXITSTATUS(s) == 143}], 0, NULL) = 11214

And bash clearly uses this knowledge, since the first one results in Terminated being printed to the terminal (oddly, this happens even if I redirect both stdout and stderr elsewhere), and the second one doesn't. How can I differentiate these two cases from a bash script?


Solution

  • wait is a syscall and also a bash builtin.

    To differentiate the two cases from bash run the program in the background and use the builtin wait to report the outcome.

    Following are examples of both a non-zero exit code and an uncaught signal. These examples use the exit and kill bash builtins in a child bash shell, instead of a child bash shell you would run your program.

    $ bash -c 'kill -s SIGTERM $$' & wait
    [1] 36068
    [1]+  Terminated: 15          bash -c 'kill -s SIGTERM $$'
    $ bash -c 'exit 143' & wait
    [1] 36079
    [1]+  Exit 143                bash -c 'exit 143'
    $
    

    As to why you see Terminated printed to the terminal even when you redirect stdout and stderr the reason is that is printed by bash, not by the program.

    Update:

    By explicitly using the wait builtin you can now redirect its stderr (with the exit status of the program) to a separate file.

    The following examples show the three types of termination: normal exit 0, non-zero exit, and uncaught signal. The results reported by wait are stored in files tagged with the PID of the corresponding program.

    $ bash -c 'exit 0' & wait 2> exit_status_pid_$!
    [1] 40279
    $ bash -c 'exit 143' & wait 2> exit_status_pid_$!
    [1] 40291
    $ bash -c 'kill -s SIGTERM $$' & wait 2> exit_status_pid_$!
    [1] 40303
    $  for f in exit_status_pid*; do echo $f: $(cat $f); done
    exit_status_pid_40279: [1]+ Done bash -c 'exit 0'
    exit_status_pid_40291: [1]+ Exit 143 bash -c 'exit 143'
    exit_status_pid_40303: [1]+ Terminated: 15 bash -c 'kill -s SIGTERM $$'
    $