Cover V04, I01
Article
Figure 1
Figure 2
Figure 3
Figure 4
Figure 5
Table 1

jan95.tar


Signals and Scripts

John Lees

This article explains what UNIX signals are and why you might want to catch them in a shell script. I take a look at the signal handling abilities of the Bourne and Korn shells, the C shell, and perl. Examples show how you can use signal handling to make your system administration scripts friendlier.

Signals

When an important asynchronous event occurs, the operating system sends a signal to the affected process. Signals are sometimes called interrupts, because they interrupt the normal execution of a process. Some events that can cause signals to be sent are hanging up your connection, typing Control-C, dividing by zero, and running out of memory.

When you type Control-C, for example, the operating system sends a SIGINT signal to the process in the foreground. If you are sitting at the shell prompt or typing a command line, this process is your login shell; otherwise, it is the process you are running (in a pipe, usually the final process).

If the process receiving the signal is a shell interpreting a script, you can write the script to catch some signals and do something appropriate in response. For example, when the user types that Control-C, the default action of a shell is to stop executing the script, which may leave an untidy mess of temporary files behind.

You can write the script to remove temporary files and perform other cleanup actions. You can even choose to ignore a Control-C, though a Control-Z can stop the process. Then the process can be killed.

Table 1 shows some signals that are found in most flavors of UNIX. These are the signals that can originate from something a user does. There are many other signals, corresponding to such asynchronous events as illegal instructions, memory errors, and so on. It generally makes no sense for a shell script to catch such signals.

Signals may be referenced by name or by number. In Bourne and C shell scripts you use the signal number; in perl, you use the name but without the "SIG". See /usr/include/sys/signal.h and the man pages for kill, signal, and termio for detailed information on the signals supported by your operating system.

You can do one of three things with these signals. Let the shell take its default action (terminate the script). Ignore the signal. Catch the signal and do something useful.

As an aside, when you fork a child process and then wait for it to terminate, you are using signal handling. The parent process receives a signal when a child process dies. The wait just puts the parent to sleep until this happens.

Scripts

There are some basic differences in the signal handling capabilities of the three script languages (the Bourne and Korn shells have the same facilities). The Bourne shell provides a facility to execute an arbitrary sequence of commands on any signal and also on normal exit (signal "0"). The latter is very handy for doing miscellaneous cleanup like removing temporary files. Different sequences of commands can be associated with different signals.

The C shell provides a transfer of control on signals, but does not implement the useful "normal exit" signal of the Bourne shell. One transfer point is used to handle all signals, and there is no way to resume processing after handling a signal. In a C shell script you can use this transfer to clean up temporary files before exiting, but that is pretty much the extent of what you can do -- just one more reason why the C shell is the last choice for writing system administration scripts. (Some other reasons are the difficulty of redirecting standard error and the lack of shell procedures.)

The rich and wonderful perl language lets you specify a routine to be executed for each signal. A common signal handler can easily tell which signal has occurred (the name of the signal is supplied to the handler routine as an argument). Version 5 of perl provides an "END" action much like that of awk that can be used to clean up at termination.

Examples

The first example is looking through the files in a user's home directory and compiling a list of files for likely removal. The list is in the common "ls -lR" format, which shows the maximum amount of information without generating excessively long path names that tend to wrap around on the monitor. The user is given several options as to how to proceed: quit, generate a file of removal commands for later execution (perhaps after editing, and to serve as a record of what is removed), or interactive removal right now. Figure 1 is an example Bourne shell implementation; Figure 2 is the perl version.

This example makes a very simple match against filename suffixes. You may want to use a longer list of suffixes, plus perhaps checking for files older than, say, two years, of size zero, larger than some maximum size, and so on. The last time my server crashed and all the disks were fsck'd, I noticed that there were over 160,000 user files. I bet there is some deadwood in there! I'm going to be encouraging my users to use this script, you betcha.

Figure 3 is an example Bourne shell script that waits thirty seconds. If the user hits Control-C, he or she is informed of the time remaining to wait, but the wait goes on. Figure 4 is an attempt at doing this in the C shell. It illustrates the shortcomings of the C shell in this area.

Figure 5 shows how to use the BEGIN and END actions in version 5 of perl. Version 5 of perl is now available and I encourage you to make the switch. Scripts written in perl version 4 will work with perl 5 with little or no modification.

Which Script Language?

The Bourne shell is undoubtedly the first choice for portable system administration scripts. This shell is found on all UNIX systems with little or no variation. If your scripts must run everywhere, use the Bourne shell.

Although the Korn shell offers many enhancements for interactive use, it is basically identical to the Bourne shell for writing scripts. The Korn shell is not commonly found on BSD systems.

Bash (The FSF's Bourne again shell) is acceptable and is freely available, but is of course not found on all systems. Because perl is likely to be available on any system that has Bash, I would not consider Bash, except that for true portability you should make certain that your Bourne shell scripts run under Bash.

The C shell is now quite common, but it is so weak that I would not use it for this purpose. The same comment applies to variants such as tcsh.

perl is the ideal choice for writing system administration scripts, but it is not yet a standard part of most operating system distributions. I have heard that Sun intends to distribute perl with Solaris, but I don't know when this will begin. I do expect that eventually perl will be found on any system that dares to call itself UNIX.

perl programs are scripts in the sense that they remain in source form. A perl script is "compiled" each time it is run, which is why you are informed of syntax errors before any action takes place. With Bourne or C shell scripts, syntax errors are not found until that part of the script is executed. (This is why funny things happen when you edit your .login file. If you start a window manager from your .login file, your login shell may still be interpreting the .login file when you quit the window manager and logout. The shell keeps a pointer to where it should resume interpreting. If you edit the .login file, that pointer may now be to the middle of a line.)

Summary

Making your scripts signal-smart is a good idea. You can leave fewer junk files lying around and make life easier for the users. Learn perl. By the time you read this, the new improved perl 5 should be widely available.

Bibliography

Schwartz, Randal. Learning Perl. Sebastopol, CA: O'Reilly & Associates, 1993. ISBN: 1-56592-042-2.

Wall, Larry, and Randal Schwartz. Programming perl. Sebastopol, CA: O'Reilly & Associates, 1991. ISBN: 0-937175-64-1.

About the Author

John Lees has an M.S. in computer science and has worked during the past twenty years about equally as a teacher, technical writer, programmer, and system administrator. His computer experience began in the days of front panels and paper tape, and he doesn't have enough fingers and toes to count the operating systems he has used. His love/hate relationship with UNIX dates to early 1985. Currently Mr. Lees is a systems analyst with the Department of Computer Science, and manager of the Pattern Recognition and Image Processing Laboratory, at Michigan State University. He is a member of ACM, Computer Professionals for Social Responsibility, the League for Programming Freedom, the Society for Technical Communication, and the TeX Users Group.