2. Handling Errors and Signals¶
Most of the examples in this book have been ignoring the fact that errors can and probably will occur. An error can occur because the directory you are trying to use does not exist, the disk is full, or any of a thousand other reasons. Quite often, you won't be able to do anything to recover from an error, and your program should exit. However, exiting after displaying a user-friendly error message is much preferable than waiting until the operating system or Perl's own error handling takes over.
After looking at errors generated by function calls, we'll look at a way to prevent certain normally fatal activities - like dividing by zero - from stopping the execution of your script; this is by using the eval() function.
Then, you'll see what a signal is and how to use the %SIG associative array to create a signal handling function.
2.1. Checking for Errors¶
There is only one way to check for errors in any programming language. You need to test the return values of the functions that you call. Most functions return zero or false when something goes wrong. So when using a critical function like open() or sysread(), checking the return value helps to ensure that your program will work properly.
Perl has two special variables - $? and $! - that help in finding out what happened after an error has occurred. The $? variable holds the status of the last pipe close, back-quote string, or system() function. The $! variable can be used in either a numeric or a string context. In a numeric context it holds the current value of errno. If used in a string context, it holds the error string associated with errno. The variable, errno, is pre-defined variable that can sometimes be used to determine the last error that took place.
Caution |
You can't rely on these variables to check the status of pipes, back-quoted strings, or the system() function when executing scripts under the Windows operating system. My recommendation is to capture the output of the back-quoted string and check it directly for error messages. Of course, the command writes its errors to STDERR and then can't trap them, and you're out of luck. |
Once you detect an error and you can't correct the problem without outside intervention, you need to communicate the problem to the user. This is usually done with the die() and warn() functions.
2.2. Example: Using the errno Variable¶
When an error occurs, it is common practice for UNIX-based functions and programs to set a variable called errno to reflect which error has occurred. If errno=2, then your script tried to access a directory or file that did not exist. Table 13.1 lists ten possible values the errno variable can take, but there are hundreds more. If you are interested in seeing all the possible error values, run the program in Listing 13.1.
Value | Description |
---|---|
1 | Operation not permitted |
2 | No such file or directory |
3 | No such process |
4 | Interrupted function call |
5 | Input/output error |
6 | No such device or address |
7 | Arg list too long |
8 | Exec format error |
9 | Bad file descriptor |
10 | No child processes |
Pseudocode |
Loop from 1 to 10,000 using $! as the loop variable. Evaluate the $! variable in a string context so that $errText is assigned the error message associated with the value of $!. Use chomp() to eliminate possible newlines at the end of an error message. Some of the messages have newlines, and some don't. Print the error message if the message is not Unknown Error. Any error value not used by the system defaults to Unknown Error. Using the if statement modifier ensures that only valid error messages are displayed. |
Listing 13.1-13LST01.PL - A Program to List All Possible Values for errno |
|
Under Windows 95, this program prints 787 error messages. Most of them are totally unrelated to Perl.
2.2.1. Example: Using the or Logical Operator¶
Perl provides a special logical operator that is ideal for testing the return values from functions. You may recall that the or operator will evaluate only the right operand if the left operand is false. Because most functions return false when an error occurs, you can use the or operator to control the display of error messages. For example:
chdir('/user/printer') or print("Can't connect to Printer dir.\n");This code prints only the error message if the program can't change to the /user/printer directory. Unfortunately, simply telling the user what the problem is, frequently, is not good enough. The program must also exit to avoid compounding the problems. You could use the comma operator to add a second statement to the right operand of the or operator. Adding an exit() statement to the previous line of code looks like this:
chdir('/usr/printer') or print("failure\n"), exit(1); print("success\n");I added the extra print statement to prove that the script really exits. If the printer directory does not exist, the second print statement is not executed.
Note |
At the shell or DOS, a zero return value means that the program ended successfully. While inside a Perl script, a zero return value frequently means an error has occurred. Be careful when dealing with return values; you should always check your documentation. |
Using the comma operator to execute two statements instead of one is awkward and prone to misinterpretation when other programmers look at the script. Fortunately, you can use the die() function to get the same functionality.
2.2.2. Example: Using the die() Function¶
The die() function is used to quit your script and display a message for the user to read. Its syntax is
die(LIST);The elements of LIST are printed to STDERR, and then the script will exit, setting the script's return value to $! (errno). If you were running the Perl script from inside a C program or UNIX script, you could then check the return value to see what went wrong.
The simplest way to use the die() function is to place it on the right side of the or operator
chdir('/user/printer') or die();which displays
Died at test.pl line 2.if the /user/printer directory does not exist. The message is not too informative, so you should always include a message telling the user what happened. If you don't know what the error might be, you can always display the error text associated with errno. For example:
chdir('/user/printer') or die("$!");This line of code displays
No such file or directory at test.pl line 2.This error message is a bit more informative. It's even better if you append the text , stopped to the error message like this:
chdir('/user/printer') or die("$!, stopped");which displays
No such file or directory, stopped at test.pl line 2.Appending the extra string makes the error message look a little more professional. If you are really looking for informative error messages, try this:
$code = "chdir('/user/printer')"; eval($code) or die("PROBLEM WITH LINE: $code\n$! , stopped");which displays the following:
PROBLEM WITH LINE: chdir('/user/printer') No such file or directory , stopped at test.pl line 3.The eval() function is discussed in the section "Example: Using the eval() Function" later in this chapter. Therefore, I won't explain what this code is doing other than to say that the eval() function executes its arguments as semi-isolated Perl code. First, the Perl code in $code is executed and then, if an error arises, the Perl code in $code is displayed as text by the die() function.
If you don't want die() to add the script name and line number to the error, add a newline to the end of the error message. For example:
chdir('/user/printer') or die("$!\n");displays the following:
No such file or directory
2.2.3. Example: Using the warn() Function¶
The warn() function has the same functionality that die() does except the script is not exited. This function is better suited for nonfatal messages like low memory or disk space conditions. The next example tries to change to the /text directory. If the connect fails, the consequences are not fatal because the files can still be written to the current directory.
chdir('/text') or warn("Using current directory instead of /text, warning");This line of code displays
Using current directory instead of /text, warning at test.pl line 2.if the /text directory does not exist. As with die(), you can eliminate the script name and line number by ending your error message with a newline. You could also use the $! variable to display the system error message.
2.3. Trapping Fatal Errors¶
There are times when reporting fatal errors and then exiting the script are not appropriate responses to a problem. For example, your script might try to use the alarm() function, which is not supported in some versions of Perl. Normally, using an unsupported function causes your problem to exit, but you can use the eval() function to trap the error and avoid ending the script.
The eval() function accepts an expression and then executes it. Any errors generated by the execution will be isolated and not affect the main program. However, all function definitions and variable modifications do affect the main program.
2.3.1. Example: Using the eval() Function¶
You can use the eval() function to trap a normally fatal error:
eval { alarm(15) }; warn() if $@;This program displays the following:eval { print(“The print function worked.\n”); }; warn() if $@;
The Unsupported function alarm function is unimplemented at test.pl line 2. ...caught at test.pl line 3. The print function worked.The $@ special variable holds the error message, if any, returned by the execution of the expression passed to the eval() function. If the expression is evaluated correctly, then $@ is an empty string. You probably remember that an empty string is evaluated as false when used as a conditional expression.
In an earlier section, "Example: Using the die() Function," you saw the following code snippet being used:
$code = "chdir('/user/printer')"; eval($code) or die("PROBLEM WITH LINE: $code\n$! , stopped");This program shows that eval() will execute a line of code that is inside a variable. You can use this capability in many different ways besides simply trapping fatal errors. The program in Listing 13.2 presents a prompt and executes Perl code as you type it. Another way of looking at this program is that it is an interactive Perl interpreter.
Pseudocode | ||||||||||||||||||
Loop until the user enters exit. Print the prompt. Get a line of input from STDIN and remove the ending linefeed.
When you run this program, you will see a > prompt. At the prompt, you can type in any Perl code. When you press Enter, the line is executed. You can even define functions you can use later in the interactive session. The program can be stopped by typing exit at the command line. If you like powerful command-line environments, you can build on this small program to create a personalized system. For example, you might need to perform a backup operation before leaving work. Instead of creating a batch file (under DOS) or a shell file (under UNIX), you can add a new command to the Perl interactive program, as in Listing 13.3.
|