# Handling Signals - Shell Scripting

Linux uses signals to communicate with processes running on the system. In the previous sections we described the different Linux signals and how the Linux system uses these signals to stop, start, and kill processes. You can also use these signals to control the operation of your shell scripts by programming your shell script to perform commands when it receives specific signals from the Linux system.

Linux signals revisited

There are over 30 Linux signals that can be generated by the system and applications. Table below lists the most common Linux system signals that you’ll run across in your Linux programming.

Table: Linux Signals

By default, the bash shell ignores any SIGQUIT (3) and SIGTERM (15) signals that it receives (this is so that an interactive shell can’t be accidentally terminated). However, the bash shell processes any SIGHUP (1) and SIGINT (2) signals it receives.

If the bash shell receives a SIGHUP signal, it exits. Before it exits though, it passes the SIGHUP signal to any processes started by the shell (such as your shell script). With a SIGINT signal, the shell is just interrupted. The Linux kernel stops giving the shell processing time on the CPU. When this happens, the shell passes the SIGINT signal to any processes started by the shell to notify them of the situation.

The shell passes these signals to your shell script program for processing. The default behavior of shell scripts, however, is to ignore the signals, which may have an adverse effect on the operation of your script. To avoid this situation, you can program your script to recognize signals, and perform commands to prepare the script for the consequences of the signal.

Generating signals

The bash shell allows you to generate two basic Linux signals using key combinations on the keyboard. This feature comes in handy if you need to stop or pause runaway programs.

Interrupting a process

The Ctrl-C key combination generates a SIGINT signal, and sends it to any processes currently running in the shell. You can test this by running a command that normally takes a long time to finish, and pressing the Ctrl-C key combination:

$sleep 100$
The Ctrl-C key combination doesn’t produce any output on the monitor, it just stops the current process running in the shell. The sleep command pauses operation for the specified number of seconds. Normally, the command prompt wouldn’t return until the timer has expired. By pressing the Ctrl-C key combination before the timer expires, you can cause the sleep command to terminate prematurely.

Pausing a process

Instead of terminating a process, you can pause it in the middle of whatever it’s doing. Sometimes this can be a dangerous thing (for example, if a script has a file lock open on a crucial system file), but often it allows you to peek inside what a script is doing without actually terminating the process.

The Ctrl-Z key combination generates a SIGTSTP signal, stopping any processes running in the shell. Stopping a process is different than terminating the process, as stopping the process leaves the program still in memory, and able to continue running from where it left off. In the ‘‘Job
Control’’ section later on you’ll learn how to restart a process that’s been stopped.

When you use the Ctrl-Z key combination, the shell informs you that the process has been stopped:

$sleep 100 [1]+ Stopped sleep 100$

The number in the square brackets is the job number assigned by the shell. The shell refers to each process running in the shell as a job, and assigns each job a unique job number. It assigns the first process started job number 1, the second job number 2, and so on.

If you have a stopped job assigned to your shell session, bash will warn you if you try to exit the shell:

$exit logout There are stopped jobs.$

You can view the stopped job by using our friend the ps command:

$ps au USER PID %CPU %MEM VSZ RSS TTY STAT START TIME COMMAND rich 20560 0.0 1.2 2688 1624 pts/0 S 05:15 0:00 -bash rich 20605 0.2 0.4 1564 552 pts/0 T 05:22 0:00 sleep 100 rich 20606 0.0 0.5 2584 740 pts/0 R 05:22 0:00 ps au$

The ps command shows the status of the stopped job as T, which indicates the command is either being traced or is stopped.

If you really want to exit the shell with the stopped job still active, just type the exit command again. The shell will exit, terminating the stopped job. Alternately, now that you know the PID of the stopped job, you can use the kill command to send a SIGKILL signal to terminate it:

$kill -9 20605$
[1]+ Killed sleep 100
$When you kill the job, initially you won’t get any response. However, the next time you do something that produces a shell prompt, you’ll see a message indicating that the job was killed. Each time the shell produces a prompt, it also displays the status of any jobs that have changed states in the shell. After you kill a job, the next time you force the shell to produce a prompt, it displays a message showing that the job was killed while running. Trapping signals Instead of allowing your script to ignore signals, you can trap them when they appear and perform other commands. The trap command allows you to specify which Linux signals your shell script can watch for and intercept from the shell. If the script receives a signal listed in the trap command, it prevents it from being processed by the shell, and instead handles it locally. The format of the trap command is:trap commands signalsThat’s simple enough. On the trap command line, you just list the commands you want the shell to execute, along with a space-separated list of signals you want to trap. You can specify the signals either by their numeric value or by their Linux signal name.Here’s a simple example of using the trap command to ignore SIGINT and SIGTERM signals:$ cat test1
#!/bin/bash
# testing output in a background job
trap "echo Haha" SIGINT SIGTERM
echo "This is a test program"
count=1
while [ $count -le 10 ] do echo "Loop #$count"
sleep 10
count=$[$count + 1 ]
done
echo "This is the end of the test program"
$The trap command used in this example displays a simple text message each time it detects either the SIGINT or SIGTERM signal. Trapping these signals makes this script impervious to the user attempting to stop the program by using the bash shell keyboard Ctrl-C command:$ ./test1
This is a test program
Loop #1
Haha
Loop #2
Loop #3
Haha
Loop #4
Loop #5
Loop #6
Loop #7
Haha
Loop #8
Loop #9
Loop #10
This is the end of the test program
$Each time the Ctrl-C key combination was used, the script executed the echo statement specified in the trap command instead of ignoring the signal and allowing the shell to stop the script. Trapping a script exit Besides trapping signals in your shell script, you can trap them when the shell script exits. This is a convenient way to perform commands just as the shell finishes its job. To trap the shell script exiting, just add the EXIT signal to the trap command:$ cat test2
#!/bin/bash
# trapping the script exit
trap "echo byebye" EXIT
count=1
while [ $count -le 5 ] do echo "Loop #$count"
sleep 3
count=$[$count + 1 ]
done
$./test2 Loop #1 Loop #2 Loop #3 Loop #4 Loop #5 byebye$

When the script gets to the normal exit point, the trap is triggered, and the shell executes the command you specify on the trap command line. The EXIT trap also works if you prematurely exit the script:

$./test2 Loop #1 Loop #2 byebye$

When the Ctrl-C key combinatIon is used to send a SIGINT signal, the script exits (since that signal isn’t listed in the trap list), but before the script exits, the shell executes the trap command.

Removing a trap

You can remove a set trap by using a dash as the command and a list of the signals you want to return to normal behavior:

$cat test3 #!/bin/bash # removing a set trap trap "echo byebye" EXIT count=1 while [$count -le 5 ]
do
echo "Loop #$count" sleep 3 count=$[ $count + 1 ] done trap - EXIT echo "I just removed the trap"$ ./test3
Loop #1
Loop #2
Loop #3
Loop #4
Loop #5
I just removed the trap
$Once the signal trap is removed, the script ignores the signals. However, if a signal is received before the trap is removed, the script processes it per the trap command:$ ./test3
Loop #1
Loop #2
byebye
\$

In this example a Ctrl-C key combination was used to terminate the script prematurely. Since the script was terminated before the trap was removed, the script executed the command specified in the trap.