< Previous | Contents | Next >
Traps
In Chapter 10, we saw how programs can respond to signals. We can add this capability to our scripts, too. While the scripts we have written so far have not needed this capabil- ity (because they have very short execution times, and do not create temporary files), larger and more complicated scripts may benefit from having a signal handling routine.
When we design a large, complicated script, it is important to consider what happens if the user logs off or shuts down the computer while the script is running. When such an event occurs, a signal will be sent to all affected processes. In turn, the programs repre- senting those processes can perform actions to ensure a proper and orderly termination of the program. Let’s say, for example, that we wrote a script that created a temporary file during its execution. In the course of good design, we would have the script delete the file when the script finishes its work. It would also be smart to have the script delete the file if a signal is received indicating that the program was going to be terminated prematurely.
bash provides a mechanism for this purpose known as a trap. Traps are implemented with the appropriately named builtin command, trap. trap uses the following syntax:
trap argument signal [signal...]
where argument is a string which will be read and treated as a command and signal is the specification of a signal that will trigger the execution of the interpreted command.
Here is a simple example:
#!/bin/bash
# trap-demo: simple signal handling demo
#!/bin/bash
# trap-demo: simple signal handling demo
trap "echo 'I am ignoring you.'" SIGINT SIGTERM for i in {1..5}; do
echo "Iteration $i of 5" sleep 5
done
trap "echo 'I am ignoring you.'" SIGINT SIGTERM for i in {1..5}; do
echo "Iteration $i of 5" sleep 5
done
This script defines a trap that will execute an echo command each time either the SIG- INT or SIGTERM signal is received while the script is running. Execution of the pro- gram looks like this when the user attempts to stop the script by pressing Ctrl-c:
[me@linuxbox ~]$ trap-demo
Iteration 1 of 5
Iteration 2 of 5 I am ignoring you. Iteration 3 of 5 I am ignoring you. Iteration 4 of 5
Iteration 5 of 5
[me@linuxbox ~]$ trap-demo
Iteration 1 of 5
Iteration 2 of 5 I am ignoring you. Iteration 3 of 5 I am ignoring you. Iteration 4 of 5
Iteration 5 of 5
As we can see, each time the user attempts to interrupt the program, the message is printed instead.
Constructing a string to form a useful sequence of commands can be awkward, so it is common practice to specify a shell function as the command. In this example, a separate shell function is specified for each signal to be handled:
#!/bin/bash
# trap-demo2: simple signal handling demo exit_on_signal_SIGINT () {
echo "Script interrupted." 2>&1 exit 0
}
exit_on_signal_SIGTERM () {
echo "Script terminated." 2>&1 exit 0
}
trap exit_on_signal_SIGINT SIGINT trap exit_on_signal_SIGTERM SIGTERM
#!/bin/bash
# trap-demo2: simple signal handling demo exit_on_signal_SIGINT () {
echo "Script interrupted." 2>&1 exit 0
}
exit_on_signal_SIGTERM () {
echo "Script terminated." 2>&1 exit 0
}
trap exit_on_signal_SIGINT SIGINT trap exit_on_signal_SIGTERM SIGTERM
for i in {1..5}; do
echo "Iteration $i of 5" sleep 5
done
for i in {1..5}; do
echo "Iteration $i of 5" sleep 5
done
This script features two trap commands, one for each signal. Each trap, in turn, speci- fies a shell function to be executed when the particular signal is received. Note the inclu- sion of an exit command in each of the signal-handling functions. Without an exit, the script would continue after completing the function.
When the user presses Ctrl-c during the execution of this script, the results look like this:
[me@linuxbox ~]$ trap-demo2
Iteration 1 of 5
Iteration 2 of 5 Script interrupted.
[me@linuxbox ~]$ trap-demo2
Iteration 1 of 5
Iteration 2 of 5 Script interrupted.
Temporary Files
One reason signal handlers are included in scripts is to remove temporary files that the script may create to hold intermediate results during execution. There is something of an art to naming temporary files. Traditionally, programs on Unix- like systems create their temporary files in the /tmp directory, a shared directory intended for such files. However, since the directory is shared, this poses certain security concerns, particularly for programs running with superuser privileges. Aside from the obvious step of setting proper permissions for files exposed to all users of the system, it is important to give temporary files non-predictable file- names. This avoids an exploit known as a temp race attack. One way to create a non-predictable (but still descriptive) name is to do something like this:
tempfile=/tmp/$(basename $0).$$.$RANDOM
This will create a filename consisting of the program’s name, followed by its process ID (PID), followed by a random integer. Note, however, that the $RAN- DOM shell variable only returns a value in the range of 1-32767, which is not a very large range in computer terms, so a single instance of the variable is not suf- ficient to overcome a determined attacker.
A better way is to use the mktemp program (not to be confused with the mktemp standard library function) to both name and create the temporary file. The mk- temp program accepts a template as an argument that is used to build the file- name. The template should include a series of “X” characters, which are replaced by a corresponding number of random letters and numbers. The longer the series of “X” characters, the longer the series of random characters. Here is an example:
tempfile=$(mktemp /tmp/foobar.$$.XXXXXXXXXX)
This creates a temporary file and assigns its name to the variable tempfile. The “X” characters in the template are replaced with random letters and numbers so that the final filename (which, in this example, also includes the expanded value of the special parameter $$ to obtain the PID) might be something like:
/tmp/foobar.6593.UOZuvM6654
For scripts that are executed by regular users, it may be wise to avoid the use of the /tmp directory and create a directory for temporary files within the user’s home directory, with a line of code such as this:
[[ -d $HOME/tmp ]] || mkdir $HOME/tmp