Wednesday, July 9, 2008

Using Traps Outside Of Shell Scripts On Unix Or Linux

Hey There,

Generally, when most folks think of the trap command, it's in the context of shell scripting. This is for good reason since it's always good practice to trap any signals that you need to handle. For instance, if you don't want your script to be crashed by someone hitting control-c (or sending a SIGINT), you can trap signal number 2 (SIGINT's corresponding numeric value) and execute some other command (or nothing) if a user tries to crash out of your script that way. It's not iron-clad/bullet-proof security, but it's good enough to protect against accidental damage or interruption. If you hit the "previous post" link a hundred times (or just check this link to our old post on trapping signals in shell and Perl ;) that should give you some idea of the advantages of trapping signals in your scripts.

Another venue, that often gets overlooked, but is just as useful (if not more so), is the use of traps in the command line interface. These can easily be introduced into a user's environment using whatever source file launches by default with the invocation of the shell (.profile for sh/ksh or .profile/.bashrc for bash, etc). Combining this functionality with security measures of a slightly lesser degree than those proposed in our post on making accounts su-only, can make controlling the average user's ability to muck with the shell that much easier to manage for the working sysadmin.

In simplistic fashion, you can test this yourself at the command line, by typing the following (this should work in sh, ksh and bash on Linux or Unix - pick a flavour:

host # trap 'echo Quit Interrupting Me!' 2

and then type a control-c character at your next shell prompt to get the following response:

host # ^C <-- This should be the control key and the "c" key pressed simultaneously. If you ever want to actually print control characters and retain their effects, check out our post on how to really write control characters.

host # Quit Interrupting Me!

host #


You can then, list out the trap you set (for kicks) and simply reset it by doing one of a few things (This is different and, in some instances, the same in sh, ksh and bash):

1. In sh: To list out the traps you've set, do:

host # trap
2: echo Quit Interrupting Me!
<-- Note that sh uses the signal "number"

and to reset them, just type:

host # trap 2
host # trap
<-- To make sure it's gone. In all shells you have to do this for each one separately. Check below for a simple way to reset them all.
host #

2. In ksh: To list out the traps you've set, do:

host # trap
trap -- 'echo Stop Interrupting Me' INT
<-- Note that ksh uses the signal's 3 letter abbreviation

and to reset them, just type:

host # trap - 2 <-- Note the additional - argument ksh requires
host # trap <-- To make sure it's gone.
host #

3. In bash: To list out the traps you've set, do:

host # trap -p
trap -- 'echo Quit Interrupting Me!' SIGINT
<-- Note that bash uses the signal definition found, most commonly, in your sys/io include file.

and to reset them just type:

host # trap - 2 <-- bash also requires the additional - argument.
host # trap
host #


On to resetting traps more efficiently!

As you've noted above, we've gone through the simple steps of setting a trap, executing it and then removing it. While this is relatively painless to do for a trap set on one signal, this can become a huge PITA if you're dealing with more than one signal (and, possibly, multiple signals from a finite pool, the size of which you're not sure, which are all possibly set with traps). Luckily, this is fairly easy to take care of.

Doing the following will wipe all of the traps you have set on all available signals in one fell swoop. Then you can walk away and get on with your life ;) Note that it's slightly different in sh/ksh and in bash.

For sh and ksh, the trap command is external, so you're restricted to it's functionality. On Solaris 8, which I'm using right now, there are no Gnu niceties. But, this is still okay. We already covered listing out all of your traps, and we can reset all of those traps for all three of the shells we're looking at today, each in one step:

1. In sh, to reset all of your traps, you'll need to do some educated guessing to figure out how many basic signals you have to work with, so that you can be sure you reset them all and don't reset way more than you need to. You can usually get this information from an include file, like /usr/include/sys/iso/signal_iso.h. Just start your range at zero and end with the highest number signal in that file. 37, for Solaris 8, is a safe number and covers more bases than most people ever cross. So, to reset the traps you have set on all 38 signals, just type:

host # trap ${seq 0 37}

The rub here is that this actually won't work on the operating system version I'm using, since it doesn't support the "seq" command or the ".." range operator. To preserve the sanctity of this noble old shell, just use a simple while loop to iterate through, like so:

host # x=0;while [ $x -lt 38 ];do trap $x;a=`expr $x + 1`;done
host # trap
<-- To double check that they're all gone.

2. In ksh, you still can't use seq on older versions of Solaris, but it will work on Linux and more recent Solaris releases, so you can cut down your "trap reset time" by doing:

host # trap - $(seq 0 37)
host # trap


3. In bash, just to sidetrack you one last time, you'll be using a shell built-in version of "trap," which has a handy flag that you can use to list out all the available signals (rather than doing it the old-fashioned way, like above):

host # trap -l
1) SIGHUP 2) SIGINT 3) SIGQUIT 4) SIGILL
5) SIGTRAP 6) SIGABRT 7) SIGEMT 8) SIGFPE
9) SIGKILL 10) SIGBUS 11) SIGSEGV 12) SIGSYS
13) SIGPIPE 14) SIGALRM 15) SIGTERM 16) SIGUSR1
17) SIGUSR2 18) SIGCHLD 19) SIGPWR 20) SIGWINCH
21) SIGURG 22) SIGIO 23) SIGSTOP 24) SIGTSTP
25) SIGCONT 26) SIGTTIN 27) SIGTTOU 28) SIGVTALRM
29) SIGPROF 30) SIGXCPU 31) SIGXFSZ 32) SIGWAITING
33) SIGLWP 34) SIGFREEZE 35) SIGTHAW 36) SIGCANCEL
37) SIGLOST


Sure, it's a bit more than you needed to know, but it tells you how many signals you have and comes in pretty handy as a quick reference :)

Now to reset all of your signal traps in bash, you can use either seq (on Linux or newer Solaris) or, even better, bash's built-in range operator (which can be used to easily copy files, as shown in our previous post regarding simplified file renaming), like this:

host # trap - {0..38}
host # trap


And you're back to square one ;)

, Mike