Traps and signals

If we are running our eatout.sh script interactively, we would not want our users to be able to press CTRL-C to break out of the script and thereby gain access to the shell prompt.

The shell gives us the ability to trap such signals (the CTRL-C).

Before explaining trap, let's take a detour and understand signals.

Signals

A signal is the means Linux uses for sending information between processes or between the kernel and a process.

Simply put, it's a way of communicating between disparate daemons or processes on the system - a little like in the days of old, where train drivers used battens to relay signals to the station master. There are many signal types. Try:

kill -l
                

which will list all the signals.

For example, signal 1 is SIGHUP, or signal hangup. The pneumonic (SIGTERM) is another means of referring to the signal number (1). If you send a SIGHUP to a process, it'll hang up the process - notice this does not mean the process will hang.

Often SIGHUP is a way of forcing a process to reread it's configuration files. For example if making changes to the SAMBA configuration file (smb.conf), then sending smbd (the SAMBA daemon/process) a SIGHUP:

kill -SIGHUP smbd
                

or

kill -1 smbd
                

will force SAMBA to reread it's configuration file (smb.conf).

Let's look at some other signals:

SIGTERM (15)
                

This signal indicates to the process that it should terminate. SIGTERM is actually a really nice signal, as it will ask the process to terminate as soon as it possibly can: "Please will you exit now". When sending a SIGTERM, the process will often need to close files, database connections, etc., and for this reason, the process will not die immediately, but exit "as soon as it possibly can".

There's another signal called:

SIGKILL (9)
                

If you send a SIGKILL to a process, it doesn't ask it nicely. It's a little like what Arnie does in the Terminator movies - "Asta-la-vista baby" i.e. Don't wait for ANYTHING, just die right now!

SIGINT (2)
                

Signal interrupt, or SIGINT, is sent if you want to interrupt a program. CTRL-C is a sequence that will interrupt a program using the SIGINT signal.

How do you use these signals?

Well for example, if you have a PID 1512, you could type:

kill -15 1512
                

This translates to killing the process with SIGTERM. The following produce the same result:

kill SIGTERM 1512
                

or

kill -SIGTERM 1512
                

or

kill -TERM 1512
                

Most of the time, I use signal 9 because I'm not a patient man.

In sum, here are the signals you may require most often:

signal meaning
0 NORMAL EXIT status
1 SIGHUP
15 SIGTERM
9 SIGKILL
2 SIGINT

Traps

That's the end of our detour, so let's look at our trap command.

Trap will allow us to trap some or all of these signals, and perform operations on the trapped signal. Let's begin by trapping the SIGINT signals.

The format of the trap command is as follows:

trap [options] [argument] [Signal specification]
                

The following excerpt [taken from the bash info page] has been summarised here for your information:

trap [-lp] [ARG] [SIGSPEC ...]
                

commands in ARG are to be read and executed when the shell receives signal SIGSPEC. e.g. trap "echo signal INTERRUPT has been trapped" SIGINT

If ARG is absent or equal to '-', all specified signals are reset to the values they had when the shell was started.

e.g.

trap -; exit 0; # at the end of your script.
                

If ARG is the null string, then the signal specified by each SIGSPEC is ignored.

e.g.

trap "" 3     # will ignore SIGQUIT
                

If ARG is not present and '-p' has been supplied, the shell displays the trap commands associated with each SIGSPEC.

trap "echo signal INTERRUPT has been trapped" SIGINT
trap -p
                

If no arguments are supplied, or only '-p' is given, 'trap' prints the list of commands associated with each signal number in a form that may be reused as shell input. e.g. As in the example above.

Each SIGSPEC is either a signal name such as 'SIGINT' (with or without the 'SIG' prefix) or a signal number.

e.g.

trap "echo cleaning up runfile; rm -rf /tmp/runfile.pid" INT
                

If a SIGSPEC is '0' or 'EXIT', ARG is executed when the shell exits.

e.g.

trap "echo cleaning up runfile; rm -rf /tmp/runfile.pid" 0
                

If a SIGSPEC is `DEBUG', the command ARG is executed after every simple command.

e.g.

trap "read" DEBUG
                

will allow you to step through your shell script 1 command at a time. See if you can explain why this would be the case?

If a SIGSPEC is 'ERR', the command ARG is executed whenever a simple command has a non-zero exit status (note: the `ERR' trap is not executed if the failed command is part of an 'until' or 'while' loop, part of an 'if' statement, part of a '&&' or '||' list, or if the command's return status is being inverted using '!'.)

e.g.

trap "echo 'the command produced an error'" ERR
                

The '-l' option causes the shell to print a list of signal names and their corresponding numbers.

Signals ignored upon entry to the shell cannot be trapped or reset.

Trapped signals are reset to their original values in a child process when it is created.

The return status is zero unless a SIGSPEC does not specify a valid signal.

Start by typing this example on the command line:

trap "echo You\'re trying to Control-C me" 2
                

After setting the trap as described above, press Cntrl-C. Now instead of sending a break to the terminal, a message is printed saying:

You're trying to Ctrl-C me
                

I could've said:

trap "echo HELP\! HELP\! Somebody PLEASE HELP. She\'s trying to kill me" 2 1
                

This will trap both of the SIGINT(2) and the SIGHUP(1) signals.

Then when I try:

kill -1 $$
                

It echoes the statement and does not perform the kill.

In many of my scripts, I trap the signals 0,1 and 2. At the top of my script I add:

trap "rm -f /tmp/tmpfiles" 0 1 2
                

If the script completes normally, or if the user terminates it, it will clean up all the temporary files that I might have used during the running of the script.

What if we want to ignore a trap completely?

Trap : 2	# perform a null-op on signal INT
                

or

trap "" 2	# perform nothing on SIGINT
                

or

trap 2		# reset the SIGINT trap
                

We can use traps in our eatout.sh script, since we certainly don't want anyone on the system to kill the menu system while a user is busy planning their busy weekend gastronomic tour or Cape Town!

At the top of our eatout.sh script we could trap the KILL and TERM signals:

trap "You can't kill me" 9 15
                

Now, while running your eatout.sh script in interactive mode, try to kill the process from another virtual terminal session with:

kill -9 eatout.sh
                

Exercises:

  1. Ensure that the user is unable to break the eatout.sh script from running with the break command.

  2. When your script exits, send a message to all users logged onto the system. Hint: see the man page for wall(1).

  3. Set a trap in your eatout.sh script that will allow you, the wizz shell programmer to step through your script one command at a time.

  4. Ensure that, on login to a new terminal, the SIGSTOP signal is trapped and a message printed accordingly. How would you test this?