45

When a signal is received, I can execute some commands using trap. Example:

trap 'echo hello world' 1 2

If any of the signals specified is received, the hello world' is displayed.

But how can I print/identify the received signal name?

codeforester
  • 39,467
  • 16
  • 112
  • 140
Lunar Mushrooms
  • 8,358
  • 18
  • 66
  • 88

5 Answers5

52

(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
perelman
  • 1,747
  • 14
  • 25
  • Thanks. But it wont work for me. That function trap_with_arg() will block until the signal is received. I want the signal name to be displayed when a signal is received - while the script is being executed. In my case I am not waiting for the signal. Whenever it arrives, need to be printed. – Lunar Mushrooms Feb 13 '12 at 07:23
  • 10
    `trap_with_arg()` doesn't block. It loops through its input, sets up the trap handlers, and then returns. – perelman Feb 13 '12 at 07:34
  • 1
    Lunar Mushrooms, it might seem to block because traps are not processed while commands in your script are running. If you signalled your script while it was in the middle of a `sleep`, it would not execute your trap until the `sleep` were complete. It is not asynchronous. The trap will catch signals between commands in your script. – Ray Apr 04 '15 at 01:13
  • 1
    instead of `read` you might want to use something like `sleep 1000 & pid=$! ; echo "waiting for ${pid}" ; wait $pid`, so its more clear what is going on – user5359531 Jul 17 '19 at 17:45
  • @user5359531 I added an `echo` of `$$` instead, which I think gets at what you were recommending. – perelman Aug 06 '19 at 08:35
  • Nice workaround, but not precisely what the OP asked for. – Dirk Jan 02 '20 at 14:54
  • @Dirk Sorry, I'm not sure what you mean. Yes, it's a workaround, but there's no way to do the task without a workaround. – perelman Jul 11 '20 at 04:18
  • @perelman, the question was if or how the trapped signal can be displayed. The answer is clearly no, and a nice way to work around that limitation is to use separate handlers for each signal, simply like `trap "echo interrupted" INT; trap "echo killed" KILL`. Your answer is more complicated than needed to explain that. – Dirk Jul 22 '20 at 13:32
  • 1
    @Dirk Ah, I see the distinction you're making. Thanks for the clarification. I've expanded my answer to give an intermediate explanation before the fully general workaround. – perelman Jul 25 '20 at 04:52
  • how would i access the output of trapped executable? – InsOp Nov 07 '20 at 19:27
  • IMO the somewhat ugly duplication writing `trap "func_trap INT" INT ; trap "func_trap TERM" TERM ; trap "func_trap EXIT" EXIT` still wins out in the long run, because maintainability. See also: answer by @yoav-kleinberger. – conny Nov 10 '20 at 09:01
15

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
Phil
  • 353
  • 4
  • 7
9
for s in {1..64}; do trap "echo trap $s" $s; done

Or without bash-isms

s=1; while [ $s -le 64 ]; do trap "echo trap $s" $s; s=$((s+1)); done

Sets 64 individual traps, one for each possible signal.


There is also a "signal" 0 that isn't included above because it isn't really a signal. No other process can send it to your process. It's really a hook, that just uses the signal/trap interface as how you install the hook. What it traps is an event rather than a signal, and the event is exit.

trap "do stuff" 0
"do stuff" is executed when the shell exits.

You could use {0..64} or s=0 above to include it.

I don't think it's useful for this question because it's not a signal you need to detect like "What signal did I just receive?"
You will always get one just before the shell exits.

But you might possibly want it to detect that it did not run, which is a way to detect KILL.

9 is included in the 1-64 above, but that particular trap will never actually execute because KILL kills instantly and the process gets no chance to do anything else at all, including run any signal handler code, for 9 or 0 or anything else.

So if you have a trap on 0 and it did not run, that tells you that the shell received a KILL. (or that the trap 0 code was so broken it couldn't tell you that it ran, or that the power cord was pulled, or that the script is still running, etc.)

I use it on practically every script for the most reliable and convenient temp file clean-up. It doesn't matter if the script ran normally or if it borked anywhere in the middle, the trap 0 code always still runs as the last thing before exiting.

Brian White
  • 389
  • 4
  • 9
  • This I found the simplest to understand and use. Thank you! (and it works) – Mint Mar 11 '23 at 01:16
  • 1
    `for s in $(seq 1 64); do trap "echo trap $s" $s; done` is a little simpler outside of bash, but does depend on seq. – rrauenza Jul 11 '23 at 00:21
8

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.

Steven
  • 151
  • 1
  • 3
  • Thanks for reporting this, I've updated my answer. Changing `read X` to `(read X)` can be used as a workaround. – Phil Feb 18 '22 at 06:21
7

a simple way to do this:

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

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