Classic Shell Scripting - Arnold Robbins [193]
SIGQUIT 3 Core Quit (see termio(7I))
...
SIGABRT 6 Core Abort
...
SIGFPE 8 Core Arithmetic Exception
...
SIGPIPE 13 Exit Broken Pipe
...
SIGUSR1 16 Exit User Signal 1
SIGUSR2 17 Exit User Signal 2
SIGCHLD 18 Ignore Child Status Changed
...
The trap command causes the shell to register a signal handler to catch the specified signals. trap takes a string argument containing a list of commands to be executed when the trap is taken, followed by a list of signals for which the trap is set. In older shell scripts, you often see those signals expressed as numbers, but that is neither informative nor portable: stick with signal names.
Example 13-3 shows a small shell script, looper, that uses trap commands to illustrate caught and uncaught signals.
Example 13-3. A sleepy looping script: looper
#! /bin/sh -
trap 'echo Ignoring HUP ...' HUP
trap 'echo Terminating on USR1 ... ; exit 1' USR1
while true
do
sleep 2
date >/dev/null
done
looper has two trap commands. The first simply reports that the HUP signal was received, whereas the second reports a USR1 signal and exits. The program then enters an infinite loop that spends most of its time asleep. We run it in the background, and send it the two signals that it handles:
$ ./looper &
Run looper in the background
[1] 24179 The process ID is 24179
$ kill -HUP 24179
Send looper a HUP signal
Ignoring HUP ...
$ kill -USR1 24179
Send looper a USR1 signal
Terminating on USR1 ...
[1] + Done(1) ./looper &
Now let's try some other signals:
$ ./looper &
Run looper again in the background
[1] 24286
$ kill -CHLD 24286
Send looper a CHLD signal
$ jobs
Is looper still running?
[1] + Running ./looper &
$ kill -FPE 24286
Send looper an FPE signal
[1] + Arithmetic Exception(coredump)./looper &
$ ./looper &
Run looper again in the background
[1] 24395
$ kill -PIPE 24395
Send looper a PIPE signal
[1] + Broken Pipe ./looper &
$ ./looper &
Run looper again in the background
[1] 24621
$ kill 24621
Send looper the default signal, TERM
[1] + Done(208) ./looper &
Notice that the CHLD signal did not terminate the process; it is one of the signals whose kernel default is to be ignored. By contrast, the floating-point exception (FPE) and broken pipe (PIPE) signals that we sent are among those that cause process termination.
As a final experiment, we add one more trap command to looper:
trap 'echo Child terminated ...' CHLD
We give the modified script a new name, and run it:
$ ./looper-2 &
Run looper-2 in the background
[1] 24668
Child terminated ...
Child terminated ...
Child terminated ...
Child terminated ...
$ kill -ABRT 24668
Send looper-2 an ABRT signal
[1] + Abort(coredump) ./looper-2 &
Each time the loop body sleep and date processes terminate, the CHLD trap is taken, producing a report every second or so, until we send an ABRT (abort) signal that terminates the looping process.
In addition to the standard signals listed earlier with kill -l, the shell provides one additional signal for the trap command: EXIT. That signal is always assigned the number zero, so trap '...' 0 statements in older shell scripts are equivalent to trap '...' EXIT.
The body of a trap '...' EXIT statement is invoked just before the exit( ) system call is made, either explicitly by an exit command, or implicitly by normal termination of the script. If traps are set for other signals, they are processed before the one for EXIT.
The value of the exit status $? on entry to the EXIT trap is preserved on completion of the trap, unless an exit in the trap resets its value.
bash, ksh, and zsh provide two more signals for trap: DEBUG traps at every statement, and ERR traps after statements returning a nonzero exit code.
The DEBUG trap is quite tricky, however: in ksh88, it traps after the statement, whereas in later shells, it traps before. The public-domain Korn shell implementation available on several platforms does not support the DEBUG trap at all. We can demonstrate these differences with a short test script:
$ cat debug-trap