Identifying received signal name in Bash

(If you only have the number of a signal and want the name, kill -l $SIGNAL_NUM prints the name of a signal; you can avoid that by using the signal names instead of numbers in your call to trap as below.)

This answer says that there's no way to access the signal name, but if you have a separate function for each signal that you trap, then you already know the signal name:

trap 'echo trapped the HUP signal' HUP
trap 'echo different trap for the INT signal' INT

In many cases, that may be sufficient, but another answer on that same question uses that fact to provide a workaround to fake the behavior you want. It takes a function and a list of signals and sets a separate trap for each signal on that function called with the signal name, so internally it's actually a separate function for each signal but it looks like a single trap on a single function that gets the signal name as an argument:

Code:

#!/bin/bash

trap_with_arg() {
    func="$1" ; shift
    for sig ; do
        trap "$func $sig" "$sig"
    done
}

func_trap() {
    echo "Trapped: $1"
}

trap_with_arg func_trap INT TERM EXIT

echo "Send signals to PID $$ and type [enter] when done."
read # Wait so the script doesn't exit.

If I run that, then I can send signals to the process and I get output like

Trapped: INT
Trapped: TERM
Trapped: EXIT

Referring to the $? solution above: $? will reflect the exit code of the last executed command. Consider this:

#!/bin/bash
trap 'echo CODE: $?; exit 1' 1 2 3 15
sleep 3600

If you run this and hit Ctrl-C, it will print CODE: 130. That's because the sleep executable was interrupted by the SIGINT and exited with that code.

Compare that to:

#!/bin/bash
trap 'echo CODE: $?; exit 1' 1 2 3 15
read X

If you run this and hit Ctrl-C, it will print CODE: 0, presumably because the read command is a builtin and exit code rules are different (same happens if you would interrupt while : ; do : ; done).

So, $? only tells you about the signal if it interrupted an external command, and if that particular program has not caught the signal and exited with its own exit code. Point in case is the bash script above: upon receiving a SIGINT, it will exit with code 1, not 130.


Within the trap (when triggered via a signal), the $? variable is initially set to the signal number plus 128, so you can assign the signal number to a variable by making the first statement of the trap action to something like

sig=$(($? - 128))

You can then get the name of the signal using the kill command

kill -l $sig

Update: As noted in the comments, this doesn't work for some builtin shell commands. To have these set the trap's $? variable, they can be run in a subshell, Eg

(read)

instead of

read

a simple way to do this:

_handler() {
   signal=$1
   echo signal was $signal
 }

 trap '_handler SIGTERM' SIGTERM
 trap '_handler SIGINT'  SIGINT

Tags:

Linux

Shell

Bash