If I redirect STDOUT, perhaps by redirecting output.txt, it’s not connected to the terminal anymore and my test program prints no message: $ perl -le 'print "Interactive!" if -t STDOUT'
Trang 1Here’s the AppConfig version of my earlier INI reader, using the same INI file that I usedearlier:
my $email = $config->get( 'network_email' );
my $author = $config->get( 'book_author' );
print "Kindly send complaints to $author ($email)\n";
This program is a bit more complicated Since AppConfig does so many different things,
I have to give it some hints about what it is going to do Once I create my $config object,
I have to tell it what fields to expect and what sorts of values they’ll have AppConfig
uses the format syntax from Getopt::Long With the INI format, AppConfig flattens thestructure by taking the section names and using them as prefixes for the values Myprogram complains about the fields I didn’t define, and AppConfig gets a bit confused
on the INI commented line ;complainneedlessly:
debugging_;complainneedlessly: no such variable at config.ini line 2
debugging_showpoderrors: no such variable at config.ini line 3
Kindly send complaints to brian d foy (brian.d.foy@gmail.com)
Now that I have that my AppConfig program, I can change the configuration formatwithout changing the program The module will figure out my new format automati-cally My previous program still works as long as I update the filename I use for theconfiguration file Here’s my new configuration format:
Trang 2my $email = $config->get( 'network_email' );
my $author = $config->get( 'book_author' );
print "Kindly send complaints to $author ($email)\n";
Now when I run my program and supply another value for network_email on the mand line, its value overrides the one from the file because I use $config->args after
com-$config->file:
$ perl appconfig-args.pl
Kindly send complaints to brian d foy (brian.d.foy@gmail.com)
$ perl appconfig-args.pl -network_email bdfoy@cpan.org
Kindly send complaints to brian d foy (bdfoy@cpan.org)
AppConfig is much more sophisticated than I’ve shown and can do quite a bit more.I’ve listed some articles on AppConfig in “Further Reading,” at the end of the chapter
Other Configuration Formats
There are many other configuration formats and each of them probably already has aPerl module to go with it Win32::Registry gives me access to the Windows Registry,
Mac::PropertyList deals with Mac OS X’s plist format, and Config::ApacheFile ses the Apache configuration format Go through the list of Config:: modules on CPAN
par-to find the one that you need
Scripts with a Different Name
My program can also figure out what to do based on the name I use for it The name
of the program shows up in the Perl special variable $0, which you might also recognizefrom shell programing Normally, I only have one name for the program However, Ican create links (symbolic or hard) to the file When I call the program using one ofthose names, I can set different configuration values:
if( $0 eq ) { do this init }
elsif( $0 eq ) { do this init }
else { default init }
Instead of renaming the program, I can embed the program in a another program thatsets the environment variables and calls the program with the right command-lineswitches and values In this way, I save myself a lot of typing to set values:
Trang 3DEBUG=0
VERBOSE=0
DBI_PROFILE=2
./program -n some_value -m some_other_value
Interactive and Noninteractive Programs
Sometimes I want the program to figure out on its own if it should give me output orask me for input When I run the program from the command line, I want to see someoutput so I know what it’s doing If I run it from cron (or some other job scheduler), Idon’t want to see the output
The real question isn’t necessarily whether the program is interactive but most likely if
I can send output to the terminal or get input from it
I can check STDOUT to see if the output will go to a terminal Using the -t file test tells
me if the filehandle is connected to a terminal Normally, command-line invocationsare so connected:
$ perl -le 'print "Interactive!" if -t STDOUT'
Interactive!
If I redirect STDOUT, perhaps by redirecting output.txt, it’s not connected to the terminal
anymore and my test program prints no message:
$ perl -le 'print "Interactive!" if -t STDOUT' > output.txt
I might not intend that, though Since I’m running the program from the commandline I still might want the same output I would normally expect
If I want to know if I should prompt the user, I can check to see if STDIN is connected
to the terminal although I should also check whether my prompt will show up where a user will see that:
some-$ perl -le 'print "Interactive!" if( -t STDIN and -t STDOUT )'
Interactive!
I have to watch what I mean and ensure I test the right thing Damian Conway’s
IO::Interactive might help since it handles various special situations to determine if
a program is interactive:
use IO::Interactive qw(is_interactive);
my $can_talk = is_interactive();
print "Hello World!\n" if $can_talk;
Damian includes an especially useful feature, his interactive function, so I don’t have
to use conditionals with all of my print statements His interactive function returnsthe STDOUT filehandle if my program is interactive and a special null filehandle otherwise.That way I write a normal print statement:
188 | Chapter 11: Configuring Perl Programs
Trang 4use IO::Interactive qw(interactive);
print { interactive() } "Hello World!\n";
I have to use the curly braces around my call to interactive() because it’s not a simplereference I still don’t include a comma after the braces I get output when the program
is interactive and no output when it isn’t
There are several other ways that I could use this I could capture the return value of
interactive by assigning it to a scalar and then using that scalar for the filehandle in
For instance, if I want to complain about the perl binary, I could check the value for
cf_email That’s supposed to be the person (or role) you contact for problems with the
perl binary, but good luck getting an answer!
#!/usr/bin/perl
use Config;
print "Send complaints to $Config{cf_email}\n";
If I want to guess the hostname of the perl binary (that is, if Config correctly identified
it and I compiled perl on the same machine), I can look at the myhostname and mydo main (although I can also get those in other ways):
#!/usr/bin/perl
use Config;
print "I was compiled on $Config{myhostname}.$Config{mydomain}\n";
To see if I’m a threaded perl, I just check the compilation option for that:
#!/usr/bin/perl
use Config;
print "has thread support\n" if $Config{usethreads};
Trang 5Different Operating Systems
I may need my program to do different things based on which platform I invoke it On
a Unix platform, I may load one module, whereas on Windows I load another Perlknows where it’s running and puts a distinctive string in $^O (mnemonic: O for Oper-ating system), and I can use that string to decide what I need to do Perl determinesthat value when it’s built and installed The value of $^O is the same as $Config{'os name'} If I need something more specific, I can use the $Config{archname}
I have to be careful, though, to specify exactly which operating system I want ble 11-1 shows the value of $^O for popular systems, and the perlport documentation
Ta-lists several more Notice that I can’t just look for the pattern m/win/i to check forWindows since Mac OS X identifies itself as darwin
Table 11-1 Values for $^O for selected platforms
I can conditionally load modules based on the operating system For instance, the
File::Spec module comes with Perl and is really a facade for several operating systemspecific modules behind the scenes Here’s the entire code for the module It definesthe %module hash to map the values of $^O to the module it should load It then
requires the right module Since each submodule has the same interface, the mer is none the wiser:
Trang 6my $module = $module{$^O} || 'Unix';
require "File/Spec/$module.pm";
@ISA = ("File::Spec::$module");
1;
Summary
I don’t have to hardcode user-defined data inside my program I have a variety of ways
to allow a user to specify configuration and runtime options without her ever looking
at the source Perl comes with modules to handle command-line switches, and thereare even more on CPAN Almost any configuration file format has a correspondingmodule on CPAN, and some formats have several module options Although no par-ticular technique is right for every situation, my users won’t have to fiddle with andpotentially break the source code
Further Reading
The perlport documentation discusses differences in platforms and how to distinguish
them inside a program
Teodor Zlatanov wrote a series of articles on AppConfig for IBM developerWorks, plication Configuration with Perl” (http://www-128.ibm.com/developerworks/linux/li brary/l-perl3/index.html), “Application Configuration with Perl, Part 2” (http:// www-128.ibm.com/developerworks/linux/library/l-appcon2.html), and “Complex Lay-
“Ap-ered Configurations with AppConfig” (http://www-128.ibm.com/developerworks/open source/library/l-cpappconf.html).
Randal Schwartz talks about Config::Scoped in his Unix Review column for July 2005: http://www.stonehenge.com/merlyn/UnixReview/col59.html.
Trang 7CHAPTER 12 Detecting and Reporting Errors
Several things may go wrong in any program, including problems in programming, bad
or missing input, unreachable external resources, and many other things Perl doesn’thave any built-in error handling It knows when it couldn’t do something, and it cantell me about errors, but it’s up to me as the Perl programmer to ensure that my programdoes the right thing, and when it can’t, try to do something useful about it
Perl Error Basics
Perl has four special variables it uses to report errors: $!, $?, $@, and $^E Each reportsdifferent sorts of errors Table 12-1 shows the four variables and their descriptions,which are also in perlvar.
Table 12-1 Perl’s special error-reporting variables
Variable English Description
$! $ERRNO and $OS_ERROR Error from an operating system or library call
$? $CHILD_ERROR Status from the last wait() call
$@ $EVAL_ERROR Error from the last eval()
$^E $EXTENDED_OS_ERROR Error information specific to the operating system
Operating System Errors
The simplest errors occur when Perl asks the system to do something, but the systemcan’t or doesn’t do it for some reason In most cases the Perl built-in returns false andsets $! with the error message If I try to read a file that isn’t there, open returns falseand puts the reason it failed in $!:
open my( $fh ), '<', 'does_not_exist.txt'
or die "Couldn't open file! $!";
The Perl interpreter is a C program, and it does its work through the library of C tions it’s built upon The value of $! represents the result of the call to the underlying
func-193
Trang 8C function, which comes from the errno.h header file That’s the one from the standard
C library Other applications might have a file of the same name The errno.h file
as-sociates symbolic constants with each error value and gives a text description for them.Here’s an excerpt from the errno.h from Mac OS X:
#define EPERM 1 /* Operation not permitted */
#define ENOENT 2 /* No such file or directory */
#define ESRCH 3 /* No such process */
In my open example, I interpolated $! in a string and got a human-readable error sage out of it The variable, however, has a dual life Scalars that have different stringand numeric values are known as dualvars.*The numeric value is the errno value fromthe C function, and the string value is a human-readable message By setting $! myself
mes-I can see both values mes-I use printf’s format specifiers to force both the numeric andstring versions of the same scalar:
for ($! = 0; $! <= 102; $!++)
{
printf("%d: %s\n", $!, $! );
}
The output shows the numeric value as well as the string value:
1: Operation not permitted
2: No such file or directory
3: No such process
The value of $! is only reliable immediately after the library call I should only use $!
immediately after the expression I want to check My next Perl statement might makeanother library call, which could again change its value, but with a different message.Also, a failed library call sets the value, but a successful one doesn’t do anything to itand won’t reset $! If I don’t check the value of $! right away, I might associate it withthe wrong statement
That’s not the whole story, though The %! hash has some magic to go along with $!.The keys to %! are the symbolic constants, such as ENOENT, from errno.h This is a magic
hash so only the key that corresponds to the current $! has a value For instance, whenPerl can’t open my does_not_exist.txt, it sets $! with the value represented by ENOENT
At the same time Perl sets the value of $!{ENOENT} No other keys in %! will have a value.This means I can check what happened when I try to recover from the failed open bytaking appropriate action based on the type of error
If Perl sees %! anywhere in the program, it automatically loads the Errno module, whichprovides functions with the same name as the errno.h symbolic constants so I can get
the number for any error I don’t have to use %! to get this, though I can load it myself,and even import the symbols I want to use:
* I can create them myself with the dualvar function in Scalar::Util
Trang 9use Errno qw(ENOENT);
print "ENOENT has the number " ENOENT "\n";
In this example program, I want to write some information to disk It’s very importantinformation, so I want to take extra care to ensure I save it I can’t simply die and hopesomebody notices Indeed, if I can’t write to the file because the disk is full, my warningmay never even make it to a logfile:
my $path = File::Spec->catfile( $dir, $file );
last if open $fh, '>', $path;
warn "Could not open file: $! \n";
if( $!{ENOENT} ) # File doesn't exist
{ # Ensure the directory is there
warn "\tTrying to make directory $dir \n";
mkdir $dir, 0755;
}
elsif( $!{ENOSPC} ) # Full disk
{ # Try a different disk or mount point
warn "\tDisk full, try another partition \n";
print $fh "Something very important\n";
Perl Error Basics | 195
Trang 10Though this is a bit of a toy example, I can see that I have a lot of power to try to recoverfrom a system error I try to recover in one of four ways, and I’ll keeping running thenaked block I’ve labeled with OPEN until it works or I’ve tried enough things (at somepoint it’s hopeless, so give up) If I can open the filehandle, I break out of the nakedblock with last Otherwise, I look in %! to see which key has a true value Only onekey will hold a true value, and that one corresponds to the value in $! If I get back anerror saying the file doesn’t exist, I’ll try to create the directory it’s going to so I knowit’s there If there’s no space left on the disk, I’ll try another disk If I don’t have theright permissions, I’ll try to reset the permissions on the file This used to be a bigproblem at one of my jobs A lot of people had admin privileges and would do thingsthat inadvertently changed permissions on important files I wrote a setuid programthat pulled the right permissions from a database and reset them I could run that fromany program and try the open again That sure beats a phone call in the middle of thenight Since then, I’ve realized the lack of wisdom in letting just anyone be root.
Child Process Errors
To tell me what went wrong with subprocesses that my programs start, Perl uses $? tolet me see the child process exit status Perl can communicate with external programsthrough a variety of mechanisms, including:
system( );
` `;
open my($pipe), "| some_command";
exec( 'some command' );
my $pid = fork(); ; wait( $pid );
If something goes wrong, I don’t see the error right away To run an external program,Perl first forks, or makes a copy of the current process, then uses exec to turn itself intothe command I wanted Since I’m already running the Perl process, it’s almost assuredthat I’ll be able to run another copy of it unless I’ve hit a process limit or run out ofmemory The first part, the fork, will work There won’t be any error in $! becausethere is no C library error However, once that other process is up and running, itdoesn’t show its errors through the $! in the parent process It passes its exit value back
to the parent when it stops running, and Perl puts that in the $? I won’t see that erroruntil I try to clean up after myself when I use close or wait:
close( $pipe ) or die "Child error: $?";
wait( $pid ) or die "Child error: $?";
The value of $? is a bit more complicated than the other error variables It’s actually aword (two bytes) The high byte is the exit status of the child process I can shift all thebits to the right eight places to get that number This number is specific to the program
I run so I need to check its documentation to assign the proper meaning:
my $exit_value = $? >> 8;
Trang 11The lower seven bits of $? hold the signal number from which the process died if it diedfrom a signal:
my $signal = $? & 127; # or use 0b0111_1111
If the child process dumped core, the eighth bit in the low word is set:
my $core_dumped = $? & 128; # or use 0b1000_000;
When I use Perl’s exit, the number I give as an argument is the return value of theprocess That becomes the high word in $? if some other Perl program is the parentprocess My exit-with-value.pl program exits with different values:
#!/usr/bin/perl
# exit-with-value.pl
# exit with a random value
exit time % 256;
I can call exit-with-value.pl with another program, exit-with-value-call.pl I call the first
program with system, after which I get the exit value by shifting $? down eightpositions:
#!/usr/bin/perl
# exit-with-value-call.pl
system( "perl exit-with-value.pl" );
my $rc = $? >> 8;
print "exit value was $rc\n";
When I run my program, I see the different exit values:
exit value was 104
If I use die instead of exit, Perl uses the value 255 as the exit value I can change that
by using an END block and assigning to $? just before Perl is going to end the program.When Perl enters the END block right after a die, $? has the value Perl intends to use asthe exit value If I see that is 255, I know I came from a die and can set the exit value
to something more meaningful:
END { $? = 37 if $? == 255 }
Errors Specific to the Operating System
On some systems, Perl might even be able to give me more information about the error
by using the $^E variable These errors typically come from outside Perl, so even though
Perl Error Basics | 197
Trang 12Perl might not detect a problem using external libraries, the operating system can setits own error variable.
As far as standard Perl is concerned, the value for $^E is usually the same as $! For thethings that the Perl language does, I’m not going to get extra information in $^E OnVMS, OS/2, Windows, or MacPerl, I might get extra information, though
That doesn’t mean that platform-specific modules can’t use $^E to pass back tion When they talk to other libraries or resources, Perl isn’t necessarily going to pick
informa-up on errors in those operations If a library call returns a result indicating failure, Perlmight treat it as nothing special The calling module, however, might be able to interpretthe return value, determine it’s an error, and then set $^E on its own
The Mac::Carbon module passes back error information from the Mac interface through
$^E, and I can use Mac::Errors to translate that information into the number, symbolicconstant, or description for the error The Mac::Glue program I use to run RealPlayer
on another machine I have hooked up to my home stereo system looks at $^E to figureout what went wrong:
$ perl mac-realplayer.pl
Trying machine
-1715: a required parameter was not accessed
If I forget to set $ENV{REALPLAYER_MACHINE_USER} or $ENV{REALPLAYER_MACHINE_PASS},Mac OS X prompts me for a username and password to access the remote machine If
Trang 13I cancel that dialog, I get a different error showing that I didn’t go through theauthentication:
warn "[ FAIL ] Win32::GetFullPathName\n" if DEBUG;
$wdir = ''; # croak "Win32::GetFullPathName: $^E";
sys-Reporting Module Errors
So far I’ve shown how Perl tells me about errors, but what if I want to tell the grammer about something that went wrong in one of my modules? I have a few ways
pro-to do this I’m going pro-to use Andy Wardley’s Template toolkit to show this since it hasall of the examples I need Other modules might do it their own way
The simplest thing to do, and probably the one that annoys me the most when I see it,
is to set a package variable and let the user check it I might even set $! myself I mean,
I can do that, but don’t mistake that for an endorsement The Template module sets the
$Template::ERROR variable when something goes wrong:
my $tt = Template->new() || carp $Template::ERROR, "\n";
Package variables aren’t very nice, though They are bad karma, and programmersshould avoid them when possible In addition to, and much better than, the packagevariable is the error class method Even if I don’t get an object when I try to create one,
I can still ask the Template class to give me the error:
my $tt = Template->new() || carp Template->error, "\n";
Reporting Module Errors | 199
Trang 14If I already have an object, I can use error to find out what went wrong with the lastoperation with that object The error method returns an error object from
Template::Exception I can inspect the type and description of the error:
a false value has problems, since 0, the empty string, or the empty list might be perfectlyvalid values from a successful execution of the subroutine That’s something I have toconsider in the design of my own systems
Suppose I have a function named foo that returns a string If it doesn’t work, it returnsnothing By not giving return a value, the caller gets no value in scalar or list context(which Perl will translate to undef or the empty list):
That’s simple to document and understand I certainly don’t want to get into a mess
of return values Down that road lies madness and code bloat as even the seeminglysimple functions are overrun by code to handle every possible error path If foo starts
Trang 15to return a different value for everything that goes wrong, I dilute the interesting parts
The error method actually lives in Template::Base, and it does double duty as a method
to set and later access the error message This function lives in the base class because
it services all of the modules in the Template family It’s actually quite slick in its plicity and utility:
Trang 16After getting the first argument, it sets up $errvar If $self is a reference (i.e., called as
$tt->error), it must be an instance, so it looks in $self->{_ERROR} If $self isn’t areference, it must be a class name (i.e., called as Template->error), so it looks in thepackage variable to get a reference to the error object Notice that Andy has to turn offsymbolic reference checking there so he can construct the full package specification forwhichever class name is in $self, which can be any of the Template modules
If there are additional arguments left in @_, I must have asked error to set the message
so it does that and returns undef.†Back in process, the return value is just what the
error method returns On the other hand if @_ is empty, it must mean that I’m trying
to get the error message, so it dereferences $errvar and returns it That’s what I getback in $error in my program
That’s it Although I may not want to do it exactly the way that Andy did, it’s the samebasic idea: put the data somewhere else and give the programmer a way to find it Return
an undefined value to signal failure
Exceptions
Perl doesn’t have exceptions Let’s just get that clear right now Like some other thingsPerl doesn’t really have, people have figured out how to fake them If you’re used tolanguages, such as Java or Python, set the bar much lower so you aren’t too disap-pointed In those other languages, exceptions are part of the fundamental design, andthat’s how I’m supposed to deal with all errors Exceptions aren’t part of Perl’s design,and it’s not how Perl programmers tend to deal with errors
Although I’m not particularly fond of exceptions in Perl, there’s a decent argument infavor of them: the programmer has to handle the error, or the program stops
eval
Having said all that, though, I can fake rudimentary exceptions The easiest methoduses a combination of die and eval The die throws the exception (meaning I have to
do it on my own), and the eval catches it so I can handle it When eval catches an error,
it stops the block of code, and it puts the error message into $@ After the eval, I checkthat variable to see if something went wrong:
eval {
;
open my($fh), ">", $file
or die "Could not open file! $!";