| |||
Links Sections Example: Using the errno Variable Chapters Part I: Basic Perl 02-Numeric and String
Literals Part II: Intermediate Perl Part III: Advanced Perl 13-Handling Errors and
Signals Part IV: Perl and the Internet 21-Using Perl with Web
Servers Appendixes |
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.
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.
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.
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.
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
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.
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.
eval { alarm(15) }; warn() if $@; eval { print("The print function worked.\n"); }; warn() if $@;This program displays the following:
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. Execute the line. If the executed code set the $@ error message variable, display the error message as a warning. |
Listing 13.2-13LST02.PL - Using Perl Interactively |
|
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.
Pseudocode |
Loop until the user enters exit. Print the prompt. Get a line of input from STDIN and remove the ending linefeed. If the inputted line begins with do#, then a custom command has been entered. Process the do#backup custom command. See if the user needs help. Otherwise, use the eval() function to execute the inputted line. If the executed code set the $@ error message variable, display the error message as a warning. |
Listing 13.3-13LST03.PL - An Interactive Perl Interpreter That Understands Custom Commands |
|
This program invokes the backup program and deletes the backup files if you enter do#backup at the > prompt. Of course, you need to modify this program to perform the customized commands you'd like to have. This technique also enables you to centralize your administrative tasks, which will make them easier to document and maintain.
Tip |
If you are running Perl on a DOS or Windows machine, consider replacing your small batch utility programs with one Perl interpreter and some customized commands. This saves on hard disk space if you use a lot of batch files because each file may take up to 4,096 bytes, regardless of its actual size. |
^C at test.pl line 22
Of course, the file name and line number
change to match the particulars of whatever script happens to be running when
Ctrl+C was pressed. The ^C notation refers to the Ctrl+C key sequence.
$SIG{'INT'} = 'IGNORE';
You can restore the default handler like
this:
$SIG{'INT'} = 'DEFAULT';
If you need to ensure that files are
closed, error messages are written, or other cleanup chores are completed, you
need to create a custom INT handle function. For example:
sub INT_handler {
# close all files.
# send error message to log file.
exit(0);
}
$SIG{'INT'} = 'INT_handler';
If the Ctrl+C key sequence is pressed
anytime after the hash assignment is made, the INT_handler function is
called instead of the default handler.
Note |
In theory, you could remove the exit() call from the signal handler function, and the script should start executing from wherever it left off. However, this feature is not working on several platforms. If you want to test your platform, run the following small program: |
sub INT_handler {
print("Don't Interrupt!\n");
}
$SIG{'INT'} = 'INT_handler';
for ($x = 0; $x < 10; $x++) {
print("$x\n");
sleep 1;
}
Note |
You should be able to press Ctrl+C while the script is counting without forcing the script to end. |
The %SIG associative array holds only entries you have created for your custom signal handler functions. So, unfortunately, you can't find out which signals are supported by looking at the array returned by keys(%SIG).
Tip |
If you are running Perl on a UNIX machine, you can run the kill -l command. This command displays a list of possible signals. |
I looked directly into the perl.exe file supplied with my Perl distribution to find out that the hip port of Perl for Win32 supports the following signals:
You can also use the %SIG hash to trap a call to the warn() and die() functions. This comes in handy if you're working with someone else's code and want to keep a log of whenever these functions are called. Rather than finding every place the functions are used, you can define a handler function as in Listing 13.4.
Pseudocode |
Define a handler for the warn() function. The error message is passed to the handler as the first element of the @_ array. Define a handler for the die() function. Define the sendToLogfile() utility function. Start the signal catching by creating two entries in the %SIG hash. Invoke the warn() and die() functions. |
Listing 13.4-13LST04.PL - How to Define Signal Handler Functions for the warn() and die() Functions |
|
When this program is done executing, the PROGRAM.LOG file contains these lines:
WARN: No such file or directory at 13lst02.pl line 22. DIE: No such file or directory at 13lst02.pl line 23.
Displaying error messages is also valuable during the programming and debugging stage. If you mistakenly type a directory name, it may take you an hour to look through the script and find the problem. Handling the No such directory error correctly in the first place will tell you what the problem is and which line of the script has the problem.
In this chapter, you saw that checking for errors usually means looking at the return value of the functions that are called. Some functions set the errno variable while others simply return true or false. While the errno variable does have a core set of values that are system independent, it also has system-dependent values. Listing 13.1 showed you how to display the error values applicable to your system.
Next, you read about the or logical operator. This operator evaluates only the right operand if the left is false. Therefore, it is useful when testing for unsuccessful functions that return false upon failure.
The die() and warn() functions are both used to display an error message. In addition, the die() function causes the script to end.
Then, the eval() function was covered. It is used to execute Perl code in a protected environment so that fatal errors will not end the script. Any error messages that do arise will be placed into the $@ special variable. All variable value changes and function definitions affect the main program.
Lastly, the signals were covered. Signals are messages sent to a process by the operating system. There is a wide range of signals, and they differ depending on which operating system you are using. The %SIG associative array is used to set up your own signal handling function.
The next chapter discusses object orientation. You learn the definition of an object, how to create one, and how to derive new objects from existing objects.