Professional Documents
Culture Documents
command. Trap allows you to catch signals and execute code when they occur.
Signals are asynchronous notifications that are sent to your script when certain
events occur. Most of these notifications are for events that you hope never happen,
such as an invalid memory access or a bad system call. However, there are one or
two events that you might reasonably want to deal with. There are also "user"
events available that are never generated by the system that you can generate to
signal your script. Bash also provides a psuedo-signal called "EXIT", which is
executed when your script exits; this can be used to make sure that your script
executes some cleanup on exit.
The most common use of the trap command though is to trap the bash-generated
psuedo-signal named EXIT. Say, for example, that you have a script that creates a
temporary file. Rather than deleting it at each place where you exit your script, you
just put a trap command at the start of your script that deletes the file on exit:
tempfile=/tmp/tmpdata
trap "rm -f $tempfile" EXIT
Now whenever your script exits, it deletes your temporary file. The syntax for the
trap command is "trap COMMAND SIGNALS...", so unless the command you want
to execute is a single word, the "command" part should be quoted.
If your cleanup needs are complex, you don't have to try to jam it all into a string
with semicolons, just write a function:
function cleanup()
{
# ...
}
Note that if you send a kill -9 to your script, it will not execute the EXIT trap before
exiting.
The other possible thing that you might like to use the trap command for is to catch
Ctrl-C so that your script can't be interrupted or perhaps so you can ask if the user
really wants to interrupt the process. As an example, I'll use the following handler
function, which warns the user on the first two Ctrl-Cs and then exits on the third:
ctrlc_count=0
function no_ctrlc()
{
let ctrlc_count++
echo
if [[ $ctrlc_count == 1 ]]; then
echo "Stop that."
elif [[ $ctrlc_count == 2 ]]; then
echo "Once more and I quit."
else
echo "That's it. I quit."
exit
fi
}
while true
do
echo Sleeping
sleep 10
done
If you run that and type Ctrl-C three times, you'll get the following output:
$ bash noctrlc.sh
Sleeping
^C
Stop that.
Sleeping
^C
Once more and I quit.
Sleeping
^C
That's it. I quit.
$
My first shot at the test script had the sleep 10 as the while condition:
while sleep 10
do
echo Sleeping
done
But that didn't work. After a bit of thought, I realized it's because when the trap
command returns, it does not resume the "sleep" command at the point it was
interrupted, nor does it restart the "sleep" command, rather it returns to the next
command after the command that was interrupted, which in this case is whatever
follows the while loop. So the loop ends and the script exits normally.
This is an important point: interrupted commands are not restarted. So if your script
needs to do something important that shouldn't be interrupted, then you can't, for
example, use the trap command to trap the signal, print a warning, and then resume
the operation like nothing happened. Rather, what you need to do if you can't have
something interrupted is disable Ctrl-C handling while the command executes. You
can do this with the trap command too by specifying an empty command to trap.
You can also use the trap command to reset signal handling to the default by
specifying a "-" as the command. So you might do something like this:
The final thing I want to look at is trapping user-defined signals to a script. Say that I
want to monitor the system log and count the number of times that sudo is run, and I
want to run the script in the background and then send it a signal whenever I want it
to display the count:
nopens=0
function show_opens()
{
echo "Seen $nopens sudo session opens"
}
trap show_opens USR1
What this does is pipes the output from journalctl (i.e., the system log) to the read
command in the loop. Inside the loop, the if-statement checks to see if the line is a
sudo command. If so, it increments a counter. The code before the loop sets a trap
for the SIGUSR1 signal, and when it's received, the "show_opens" functions prints
out the number of sudo commands seen since the script was started. You can send
the SIGUSR1 signal to the script with the kill command:
Unfortunately, once again, this failed to work. The first problem I discovered, which I
recently mentioned in my post on Job Control
(https://www.linuxjournal.com/content/job-control-bash-feature-you-only-think-you-
dont-need) is that if the sudo command needs to prompt for a password, the script
becomes suspended just after starting.
After figuring out the suspended background problem, I figured all systems were
"go", but not so, still nothing. The problem now is because the loop is taking input
from a pipe. The original bash process has now executed one sub-process for
"journalctl" and another sub-process for "while read line ...". When bash executes a
command, per the man page:
traps caught by the shell are reset to the values inherited from the shell's parent
So when these sub-processes are started, the SIGUSR1 trap is reset and no longer
has an effect on the process. To get this to work, we need to set the trap inside the
loop so that it is part of the sub-process:
nopens=0
function show_opens()
{
echo "Seen $nopens sudo session opens"
}
Note that I use $BASHPID to get the process of the sub-process ($$ always returns
the process id of the original shell).
In the end, I can't say that I'll likely be trapping SIGINT or any other signals beyond
EXIT on a regular basis, but I can say that I discovered some interesting subtleties
about bash in the process of making these examples work.