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
Wednesday, July 9, 2008
Using Traps Outside Of Shell Scripts On Unix Or Linux
Wednesday, June 18, 2008
Pinging And Checking Port Status With Perl CGI On Linux And Unix
Greetings,
To change things up a bit today, we're going to go back to some Perl scripting. It seems like it's been a while, but that may just be my distorted sense of space and time. In any event, since this blog covers many many things related to Linux and Unix (which my probable life-span wouldn't allow me to script out or write about ;), today seems a good a day as any to get back to putting out some script work.
Today's Perl script is very simply written and, although a bit lengthy, fairly limited in what it does. Of course, it should also be fairly simple to expand upon and make do much more work than would normally be commensurate with a general breakdown of the keystroke/output-usefulness ratio.
This script closely echoes previous scripts we put out to check on web server status and check on network server port-health insofar as the end result is concerned. It should run fairly simply, too (you'll probably just need to change the target host, target port and, possibly, the location of the ping command, and its arguments, to suit your taste - or have those all fed to the script from the command line using the @ARGV array):
host # ./portpinger.pl
This version, however, is a bit more complex (or convoluted, depending on how you look at it ;) to highlight a few other concept-based posts that we've put out in the interim. For instance, this Perl script (while it's not absolutely necessary, given the abundance of variable names we could have used to get-around) makes use of variable scoping within subroutines. This is something, actually, that we're building toward in our ever-expanding series on porting code between shell, Perl and awk. And the final thing we highlight, somewhat, in this particular script (that my green-screen-addled brain can still discern ;) is signal trapping and handling with Perl.
Whether or not you have any use for it, I hope you can find something in its over-production that sparks some interest or gets you thinking more about the many different ways you can use Perl to do many different things. Basically, this script does a ping, a port check and then puts up a CGI web page. But, sometimes, the lessons (good or bad) are found more in the context than in the message :)
Cheers,
This work is licensed under a
Creative Commons Attribution-Noncommercial-Share Alike 3.0 United States License
#!/usr/bin/perl
#
# portpinger.pl - Ping a Port and Check Another One Just for kicks.
#
# 2008 - Mike Golvach - eggi@comcast.net
#
# Creative Commons Attribution-Noncommercial-Share Alike 3.0 United States License
#
use Socket;
use CGI;
$socketpinger = new CGI;
$pingee = "www.xyz.com";
system("ping $pingee 1 1 2>/dev/null 1>/dev/null");
$pingyn = $? >> 8;
print $socketpinger->header();
print "<html><head><title>Caps Checker</title></head>\n";
print "<body>\n";
if ( $pingyn ) {
print "<center><h3>Result of ping to $pingee:</3></center> <center><h2>N
o Answer</h2></center>\n";
} else {
print "<center><h3>Result of ping to $pingee:</h3></center> <center><h2>
$pingee is alive!</h2></center>\n";
}
print "<center><h3>Result of tcp connect to port 443:</h3></center>\n";
&checkhost($pingee,443);
print $socketpinger->end_html();
sub Timer { die "Alarm Clock\n"; }
sub checkhost {
local($host,$port) = @_;
local($t,$cnt,@var,$ip,$down);
undef @fdata;
$AF_INET=2; $PF_INET=$AF_INET; $SOCK_STREAM=1; $IPPROTO_TCP=6;
$sockaddr = 'S n a4 x8';
($t,$t,$t,$t,@var) = gethostbyname($host);
$ip = $var[0];
$down = 0;
$this = pack($sockaddr, $AF_INET, 0, "\0\0\0\0");
$serveraddr = pack($sockaddr, $AF_INET, $port, $ip);
eval 'socket(RS, $PF_INET, $SOCK_STREAM, $IPPROTO_TCP)|| die "socket: $!"';
if ($@) {
$SOCK_STREAM=2;
socket(RS, $PF_INET, $SOCK_STREAM, $IPPROTO_TCP) || die print "socket: $
!";
}
bind(RS, $this) || ($down = 1);
if ($down) {
print "<center><h2>$host at port $port is down.</h2></center>\n";
shutdown(RS,2);
close(RS);
return;
}
$SIG{'ALRM'} = 'Timer';
eval {
alarm(5);
connect(RS, $serveraddr) || die ($down = 1);
};
alarm(0);
if ($down || $@ =~ /Alarm Clock/) {
print "<center><h2>$host at port $port is down.</h2></center>\n";
shutdown(RS,2);
close(RS);
return;
}
$up[$num] = 1;
print "<center><h2>Connection to $host at port $port successful.</h2></cente
r>\n";
shutdown(RS,2);
close(RS);
}
, Mike
Tuesday, May 13, 2008
Killing Zombie Processes In Linux And Unix
Greetings,
Today's post is going to deal with "zombie" processes. These are processes to which the definition of a process only loosely applies.
A zombie process is most often generated when a parent process loses track of its child process and that child process becomes detached. The parent process, generally running some sort of a "wait()" call to receive notification that the child process has exited, loses track of the child process and never receives that information. The child process exits normally, but the parent thinks it's still running, and thus is a zombie process born :)
There are a number of steps to take, from simplest to most obscure, to get rid of zombie processes. And then there's what to do if none of that seems to work. Here we go :)
1. First, identify the fact that you have zombie processes running on your system (you may not notice, and there's a good reason why, which we'll address near the end of this post). You can do this on most major brands of Unix and Linux by running:
host # ps -el|grep Z <--- The -l flag to ps will include the "state" column. The zombie state is represented by a capital Z.
On Solaris 9:
host # ps -el|grep Z F S UID PID PPID C PRI NI ADDR SZ WCHAN TTY TIME CMD
0 Z 0 3038 1 0 0 - - 0 - ? 0:00
0 Z 0 19769 2966 0 0 - - 0 - ? 0:00
On SUSE Linux 9:/home/ymdg001# ps -el|grep Z
F S UID PID PPID C PRI NI ADDR SZ WCHAN TTY TIME CMD
0 Z 0 476 9874 0 0 - - 0 - ? 0:00
2. Now that you know you have zombie processes running, the first, and easiest, thing you can do to kill them is to try and "assassinate" the actual defunct (zombie) process. For the process in the SUSE example above, you can always try:
host # kill -9 476
It probably won't work, but it's worth a shot. Sometimes it does and your troubles are over just like that :)
3. Next, you should try to kill the parent process. This may or may not be possible for you. For instance, the parent process may be a process that you "need" to have running. It may also be a process that the Operating System "needs" to have running (like "init" - process 1, shown in the Solaris example above, under the PPID column).
Killing the parent process (if possible) will almost always work to get rid of a zombie process.
Please "never" try to kill init (process 1). If you're successful, your machine will go down hard and fast!
4. Assuming none of the above worked, some common wisdom says you should just give up (and for good reason, which we'll get to very soon ;). However you can try killing both the zombie process (and/or the parent process) using signals other than SIGKILL (or -9). I've seen it happen more than a few times. Different programs trap, and/or handle, different signals different ways. If your zombie doesn't go away when you execute a "kill -9" against it, try a simple "kill" (Which is, technically "kill -15" or SIGTERM). You can try to kill the process with any signal you want. I generally try signals 1 - 15 and then SIGUSR1 and SIGUSR2, just in case they're defined differently for that particular program on that particular system. You'd be surprised how many zombies you can whack with a SIGHUP or SIGINT. Sending a kill SIGCHLD or SIGCLD (Which is the same as SIGCHLD on System V) is a good one to try, as well. Sometimes your chosen method won't make "textbook sense" but it will work from time to time :)
You can find a handy list of signals to try in our old post on translating signal names to numbers and vice versa.
5. And the point I've been alluding to throughout this entire post.
What to do if your zombie process just won't die, you can't kill the parent and/or you're otherwise stuck?
The answer is: nothing.
Here's a brief explanation why: Even though zombie processes alarm most casual users of Unix and/or Linux, and they can make the process table look ugly with all those "defunct" messages scattered in between everything else, a zombie process lives up to its name in more ways than the sense defined above. It literally is like the somewhat-living dead. Although the proc table (and filesystem) have space reserved to record it, the process has already exited and is not consuming any of your system resources. It takes up none of your kernel or system space and is only a minor nuisance since "times" keeps track of its time (If you're a fly, you'll notice the 0:00 slow-down ;)
6. But WAIT!
There's more... (I'm starting to sound like a pitch man ;). Here's one last thing you can do if that ps entry for your zombie process is really bugging you: Once the zombie has totally disconnected from its parent process, you can just use the "wait" command to make it go away. For example:host # ps -el|grep Z
F S UID PID PPID C PRI NI ADDR SZ WCHAN TTY TIME CMD
0 Z 0 3038 1 0 0 - - 0 - ? 0:00
host # id
uid=0(root) gid=0(root)
host # wait 3038
...and when that returns (I'd recommend that you run this with "&" to background it - e.g. "wait 3038 &")host # ps -el|grep Z
F S UID PID PPID C PRI NI ADDR SZ WCHAN TTY TIME CMD
It's gone :)
In any event, hopefully, after reading this, you'll no longer worry about zombies :)
Cheers,
, Mike
linux unix internet technology
Sunday, April 27, 2008
Perl Script To Translate Signal Names And Numbers
Hey There,
For this "Lazy Sunday" post I threw together a quick little Perl script to help out further with what we went over more thoroughly in our previous post on signal definitions in Linux and Unix.
This script takes at least one argument (or it throws a fit and refuses to cooperate with you ;) of either a signal name or signal number, which it will then translate to the other (does the way I wrote that make sense? ;)
So, if you want to know what signals 4 and 5 do, you could run:
host # ./siggy.pl 4 5
Signal Number 4 = ILL
Signal Number 5 = TRAP
And see the (somewhat more verbose) translations from the system includes. Conversely, you could want to know what numbers the TERM and KILL signals are, which you could get by running:
host # ./siggy.pl TERM KILL
Signal Name TERM = 15
Signal Name KILL = 9
Or you can mix and match. Do whatever, you want. If you throw the script an argument it doesn't understand it will react accordingly ;)
host # ./siggy.pl hup 8 ... 9
Signal Name HUP = 1
Signal Number 8 = FPE
...: What is that supposed to mean?
Signal Number 9 = KILL
One interesting thing in the script is the use of an absolute subroutine call. Even though I put the "use Sys::SigAction;" line in the script, Perl was interpreting the function calls (like sig_number()) as belonging to the Main:: module. Pretty much every function, or command, in Perl does. "print," for example, is actually Main::print, so I had to use "absolute" naming. Writing "Sys::SigAction::sig_number()" (in this instance) is one way to make sure that Perl knows exactly where to look for that subroutine to run :)
Cheers,
This work is licensed under a
Creative Commons Attribution-Noncommercial-Share Alike 3.0 United States License#!/usr/bin/perl
#
# siggy.pl
#
# 2008 - Mike Golvach - eggi@comcast.net
#
# Creative Commons Attribution-Noncommercial-Share Alike 3.0 United States License
#
if ( $#ARGV < 0 ) {
print "Usage: $0 signalNumber1 [signalNumbern...]\n";
exit(1);
}
use Sys::SigAction;
@signos = @ARGV;
foreach $sig (@signos) {
if ( $sig =~ /[A-Za-z]+/ ) {
$sig = uc($sig);
$signum=Sys::SigAction::sig_number( $sig );
print "Signal Name $sig = $signum\n";
} elsif ($sig =~ /[0-9]+/) {
$signame=Sys::SigAction::sig_name( $sig );
print "Signal Number $sig = $signame\n";
} else {
print "$sig: What is that supposed to mean?\n";
}
}
, Mike
linux unix internet technology
Friday, April 25, 2008
Determining Signal Definitions In Linux and Unix
Greetings,
Today, we're going to take a quick look at simple return codes (or errno values) that get returned by the bash and ksh shells in Linux (and Solaris Unix). This complements a post we did a while back on trapping signals in Perl and the shell, and adds a bit more to understanding of the fork-and-exec process introduced in our post on running a login shell on a network port.
While these signals are subject to change, the POSIX standard is fairly consistent between most Unix and Linux systems. If you ever need to find out what a signal means, you have a number of options at your disposal to determine that information.
1. At the process level on Solaris, you can figure out what signals are associated with what processes by using the "psig" command. This command will list out all signals associated with a given process (assuming you know its process ID). A sample of this process-finding process would be something like:host # ps -ef|grep "[l]pd"
daemon 2073 1 0 Mar 21 ? 0:00 /usr/local/sbin/lpd
host # psig 2073
2073: /usr/local/sbin/lpd
HUP caught sigacthandler
...
ILL default
...
PIPE ignored
...
basically, you'll get a ton of output. And none of it may make any sense to you. That's okay for now :)
2. At the process level on Linux, you can get approximately the same functionality from the "crash" command ( used interactively, if available ) or, even better, this publicly available script written specifically to emulate Solaris' psig functionality, for use on Linux. It's called psig.sh for Linux and is an excellent tool for replicating the psig experience for Linux users who are used to Solaris' proc commands.
3. On either Linux or Unix (any flavor), you can determine what signals are handled by your machine, what their names are and what their signal numbers are, by checking your system include files. On Solaris, the header file that contains this information is almost guaranteed to be named /usr/include/sys/iso/signal_iso.h (at least for Solaris 9 and 10). In Linux, it will generally be very specific to the architecture of your build system (something like: /usr/include/asm-x86_64/signal.h)
In any event, you can follow a simple process to figure out where your system's signal definitions are loaded, no matter what flavor of Unix or Linux you're running. It all comes down to derivation and, luckily, you'll always have the same starting point (I may be wrong on this, but I've never experienced it ;).
Here's a quick command line walkthrough of two different ways to figure out where your signal definitions are listed (that is, the include/header file that defines the signal numbers, their abbreviated signal names and definitions) that can be used on almost any *nix system. As a starting point, /usr/include/signal.h is probably the best, since it exists on most systems. We'll also use SIGHUP (the standard HangUp signal and signal number 1) to guide our search:
The painful way:host # grep SIGHUP /usr/include/signal.h|grep -w 1
host # grep include /usr/include/signal.h
#include <sys/feature_tests.h>
#include <sys/types.h>
...
host # grep include /usr/include/signal.h|sed 's/^.*<\([^>]*\)>/\1/'|xargs -ivar grep SIGHUP /usr/include/var
host # grep include /usr/include/signal.h|sed 's/^.*<\([^>]*\)>/\1/'|xargs -ivar grep "^#include" /usr/include/var|grep sig
#include <sys/iso/signal_iso.h>
#include <sys/siginfo.h>
...
... and so on, and so on, until you find the right file. Basically, just follow the includes until you reach the end. This can be tedious and time consuming.
The incredibly easy way:
<--- This is our file :)Solaris_9_or_10_host # find /usr/include|xargs grep SIGHUP /dev/null|grep -w 1
/usr/include/sys/iso/signal_iso.h:#define SIGHUP 1 /* hangup */
or
<--- Note that all 3 of these entries are valid, with this header file being the most generic. Our next command can narrow down the most "specific" correct result.Linux_host # find /usr/include|xargs grep SIGHUP /dev/null|grep -w 1
/usr/include/bits/signum.h:#define SIGHUP 1 /* Hangup (POSIX). */
<--- Now we know that, if we want to be extra sure, the "/usr/include/asm-x86_64/signal.h" is the signal definition file we should be looking at to determine our signal information./usr/include/asm-x86_64/signal.h:#define SIGHUP 1
/usr/include/asm-i386/signal.h:#define SIGHUP 1
Linux_host # uname -pr
2.6.5-7.286-smp x86_64
And that's all there is to it :) As an interesting bit of trivia, if you receive an errno value from a process that's higher than 128, you can easily deduce what signal stopped that process. All you need to do is deduct 128 from the return code, like this:
host # sleep 2
host # echo $?
0
host # sleep 200
^C
host # echo $?
130
In this case, when run normally, our sleep command exited with a return code of 0 (success). When we did a control-C during the sleep command, it returned a code of 130. Of course, you won't find this signal number defined in any of your include files, but, utilizing the shell's native reporting of signal interrupts, all you need to do is subtract 128 from 130 and you now know that the sleep command was killed by a signal 2, which is defined as:
SIGINT 2 /* interrupt (rubout) */ <--- on Solaris
and
SIGINT 2 /* Interrupt (ANSI). */ <--- on the Linux box I'm using.
Both answers, although formatted differently, indicate the same signal. Nice :)
Below, I've listed the standard signals for your convenience (from a fairly generic file - signum.h) and take no credit for writing them myself ;)
Enjoy,
Some Basic signals from /usr/include/bits/signum.h on Linux (Note that I'm only listing the basic signals and not I/O polling signals, etc)#define SIGHUP 1 /* Hangup (POSIX). */
#define SIGINT 2 /* Interrupt (ANSI). */
#define SIGQUIT 3 /* Quit (POSIX). */
#define SIGILL 4 /* Illegal instruction (ANSI). */
#define SIGTRAP 5 /* Trace trap (POSIX). */
#define SIGABRT 6 /* Abort (ANSI). */
#define SIGIOT 6 /* IOT trap (4.2 BSD). */
#define SIGBUS 7 /* BUS error (4.2 BSD). */
#define SIGFPE 8 /* Floating-point exception (ANSI). */
#define SIGKILL 9 /* Kill, unblockable (POSIX). */
#define SIGUSR1 10 /* User-defined signal 1 (POSIX). */
#define SIGSEGV 11 /* Segmentation violation (ANSI). */
#define SIGUSR2 12 /* User-defined signal 2 (POSIX). */
#define SIGPIPE 13 /* Broken pipe (POSIX). */
#define SIGALRM 14 /* Alarm clock (POSIX). */
#define SIGTERM 15 /* Termination (ANSI). */
#define SIGSTKFLT 16 /* Stack fault. */
#define SIGCLD SIGCHLD /* Same as SIGCHLD (System V). */
#define SIGCHLD 17 /* Child status has changed (POSIX). */
#define SIGCONT 18 /* Continue (POSIX). */
#define SIGSTOP 19 /* Stop, unblockable (POSIX). */
#define SIGTSTP 20 /* Keyboard stop (POSIX). */
#define SIGTTIN 21 /* Background read from tty (POSIX). */
#define SIGTTOU 22 /* Background write to tty (POSIX). */
#define SIGURG 23 /* Urgent condition on socket (4.2 BSD). */
#define SIGXCPU 24 /* CPU limit exceeded (4.2 BSD). */
#define SIGXFSZ 25 /* File size limit exceeded (4.2 BSD). */
#define SIGVTALRM 26 /* Virtual alarm clock (4.2 BSD). */
#define SIGPROF 27 /* Profiling alarm clock (4.2 BSD). */
#define SIGWINCH 28 /* Window size change (4.3 BSD, Sun). */
#define SIGPOLL SIGIO /* Pollable event occurred (System V). */
#define SIGIO 29 /* I/O now possible (4.2 BSD). */
#define SIGPWR 30 /* Power failure restart (System V). */
#define SIGSYS 31 /* Bad system call. */
#define SIGUNUSED 31
#define _NSIG 65 /* Biggest signal number + 1
, Mike
linux unix internet technology
Tuesday, November 13, 2007
Trapping Signals - Perl vs. Shell
Hey there,
It's probably a fact that most scripts you ever check out (Perl or shell) perform a function. They maybe even do some error checking and argument parsing. It's strange that it's relatively rare to see a script that actually traps signals and reacts to them. I've actually been asked by folks, that I know can script, what the heck the "trap" line at the top of one of my shell scripts is for.
The idea of trapping signals probably just doesn't fit well with the way the world works now. A lot of safety mechanisms are built into programming and scripting languages and, therefore, the scripter or programmer has less to worry about. Java will clean up your garbage for you. My kid won't even do that for me if I pay him :)
There are plenty of times, though, that it makes sense to trap signals in your scripts (or write a function file or something like that) so that you can maintain some sense of virtually guaranteed order.
For instance, if you've got a great script that works like a charm, it could still have some holes in it. Perhaps you write out to temporary files to make editing alterations, or touch lock files to prevent multiple versions of your script from running simultaneously. Any of these things could become an issue the next time your script is run if, for instance, a user types control-C right in the middle of running it. Now you've got temporary files or locks set that shouldn't be, and your next script run will act logically and assume that conditions exist that actually don't.
Thankfully, it's very easy to take care of these sorts of conditions in both the shell and Perl. In the shell, I usually include a line right near the top that looks something like this (here we're just worried about the control-C):
trap 'rm /tmp/.lockfile;exit' 2
Simple enough, right? That line makes it so your script will catch that control-C (Represented on the right side by the signal's number: 2) the user enters while your script is running and execute the code block in between the single quotes. In this instance we're deleting our .lock file and exiting cleanly. Multiple commands can be put together using semi-colons, just like on the command line.
In Perl it's pretty much the same but can be a little more complex, or maybe convoluted. For our example, using it to do anything special would be overkill, but Perl has much more robust signal handling capabilites that we aren't using here. That same trap line, above, in Perl would look like this - almost always has to be a two-parter unless you just want to use default handlers like 'IGNORE' or 'DEFAULT':
$SIG{'INT'} = 'INT_handler';
then somewhere else down the script you've got your handler subroutine:
sub INT_handler {
unlink("/tmp/.lock");
exit(0);
}
When it comes right down to it, depending on what you want to do, the shell suits certain purposes more simply, and Perl suits most of the others. There are probably 10, 20 or more custom Perl modules out there to make the job even easier.
We'll take a look at signal handling and how it can enhance your scripts usability in a future post. For now, please stop INTerruping :P
Best Wishes,
, Mike
linux unix internet technology





