? ? 在很多情况下,系统调用可能会失败;例如,尝试打开不存在的文件,或者删除某个仍含有文件的目录,或者尝试读取没有读权限的文件。在前面的示例中,我们已经用到了die函数,本节将 详细讨论有关错误处理和错误处理函数的相关内容。这些函数包括die函数、warn函数和eval函数。
die函数用于在命令或文件句柄失败时退出Perl脚本。
warn函数类似于die函数,但它不会退出脚本。
eval函数具有多种用途,但它主要还是用于异常处理。
读者想必还记得短路运算符&&和||,这两个运算符首先会求其左侧操作数的值,然后才会求其右侧操作数的值。如果&&左侧操作数值为true,则求其右侧的操作数。如果||左侧操作数的值为 false,这才求其右侧的操作数。
Carp.pm模块。 有很多种退出脚本的途径可供用户选择。Perl 5提供的Carp模块扩展了die和warn的功能。(详见示例12.10。)
18.4.1 die函数
如果系统调用失败的话,die函数会把字符串打印到STDERR上,并以$!的当前值退出脚本。$!变量中含有errno的当前值,后者是一个UNIX全局变量,含有一个表示系统错误的数字。只有在系 统调用失败时才会更新errno的值。当系统调用失败时,会向errno赋予一个数字代码,以表明错误的类型。如果在字符串中省略了换行符,则会打印出带有行号的消息(参 见/usr/include/sys中的完整列表)。
下面是/usr/include/sys/errno.h文件中的示例:
#define EPERM 1 /* Not owner */#define ENOENT 2 /* No such file or directory */#define ESRCH 3 /* No such process */#define EINTR 4 /* Interrupted system call */#define EIO 5 /* I/O error */
Win32的错误代码不同于UNIX错误代码,因此不能依赖于$!返回的值。有很多Win32扩展都提供了自己的错误函数,以便为用户提供更有意义的结果。详情可参阅ActiveState中标准Perl库里的 Win32::GetLastError相关文档。
格式
die(LIST)die LISTdie
示例18.66
(In Script)1 die "Can't cd to junk: $!n" unless chdir "/usr/bin/junk";(Output)1 Can't cd to junk: No such file or directory
解释
1. chdir调用失败。$!中含有来自errno的错误消息。换行符导致打印了die函数后面的字符串,该字符串中含有变量$!的值。
示例18.67
(In Script)1 die unless chdir '/plop' ;(Output)1 Died at croak.perl line 4.
解释
1. chdir调用失败。这一次$!并不在die字符串中,打印发生错误的行。
示例18.68
(In Script)1 chdir '/plop' or die "Stopped";(Output)1 Stopped at croak.perl line 4.
解释
1. 本示例的输出内容与前一个示例相同,但它使用的不同的语法。如果chdir调用失败,就执行or右边的die函数。
warn函数
warn函数(运算符)和die几乎一样,所不同的是前者会让程序继续运行。如果在eval块中调用die函数,则传递给die的参数字符串将同样赋值给特殊变量$@。在调用die之后,该变量可以作为一个参数传递给warn函数,其输出将发送到STDERR上(参阅"ecal函数"一节)。
eval函数
eval函数用于处理异常,即捕捉错误。位于eval之后的语句块是作为单独的Perl程序来处理和解析的,但其所有的变量设置、子例程以及格式定义都将保持到eval执行完毕。
eval函数返回的值是上一个表达式的值。如果出现了编译或运行时错误,或者执行了die语句的话,则会返回未定义的值,并将特殊变量$@设置为错误消息内容。如果没有发生错误,则$@将是空字符串。
借助eval求Perl表达式的值
示例18.69
(The Script)#!/bin/perl# The eval function will evaluate each line you type# and return the result. It's as though you are# running a little independent Perl script.# Script name: plsh1 print "> "; # Print the prompt2 while(<STDIN>){3 $result=eval ; # eval evaluates the expression $_4 warn $@ if $@; # If an error occurs,it will be assigned toprint "$resultn if $result";6 print "> "; # Print the prompt}(Output)(The Command line)$ plsh2 > hello5 hello2 > bye5 bye2 > 5 + 45 92 > 8 / 35 2.666666666666672 > 5 / 04 Illegal division by zero at (eval 5) line 3,<STDIN> line 5.> "Oh I seeCan't find string terminator '"' anywhere before EOF at (eval 6)line 1,<STDIN> line> exit
解释
1. 本行向用户打印出一个提示信息。该程序类似于小型Perl shell。它能帮助用户在把表达式放进程序之前检查它的好坏,特别是在不确定Perl会如何处理该表达式时。
2. 进入while循环。每次进入循环时,就从用户处读取一行输入,并赋值给$_。
3. 不带参数的eval将对$_中的表达式求值,并将求值结果赋予$result。
4. 如果eval发现了求值表达式引起的语法错误或系统错误的话,就将返回的错误信息赋值给变量$@。如果没有发现错误,则把$@赋值为空字符串。
5. 如果对表达式求值成功,打印其结果。
6. 显示提示信息,并再一次进入循环。
使用eval捕捉程序中的错误
示例18.70
(In Script)#!/bin/perlprint "Give me a number.";chop($a=<STDIN>);print "Give me a divisor.";chop($b=<STDIN>);1 eval{ die unless $answer = $a/$b ; };2 warn $@ ifprintf "Division of %.2f by %.2f is %.2f.n",$a,$b,$answer if $answer ;4 print "I'm here now. Good-day!n";(Output)Give me a number.45Give me a divisor.63 Division of 45.00 by 6.00 is 7.50.4 I'm here now. Good-day!(Output)Give me a number.5Give me a divisor.02 Illegal division by zero at ./eval.p line 8,<STDIN> line 2.4 I'm here now. Good-day!
解释
1. eval函数会计算除法($a/$b),并将结果保存到$answer中。请注意,必须首先在eval中使用$answer,该会一直保留到eval执行结束。
2. 如果一切正常,并且除法运算顺利完成,则忽略这一行。如果发现错误(例如除以0),则将$@变量设置为系统错误信息,然后通过warn函数将消息打印到STDERR,并恢复程序执行。如果在eval块中调用了die函数,则程序不退出,而是在退出eval块之后继续执行。
3. 如果运行成功,则打印除法运算的结果。
4. 打印该行内容只是为了说明,程序即使失败了也会继续执行,因为warn函数不会导致脚本退出。
eval函数和here文档
示例18.71
(The Script)#!/bin/perl1 eval<<"EOF";2 chdir "joker" || die "Can't cd: $!n";3 EOF4 print "The error message from die:print "Program $0 still in progress.n";(Output)4 The error message from die: Can't cd: no such file or directory5 Program ./eval4.p still in progress.
解释
1. here文档类似于一种特殊形式的引用。eval函数获得位于第一个EOF和最后一个EOF之间的所有内容。
2. 如果chdir函数调用失败,则调用die函数,并在here文档的最后一个EOF之后恢复程序。
3. EOF表明here文档到此结束。
4. 将die函数的错误消息保存在变量$@中。
5. 程序继续执行。
对数据库的读取用DBI时可以设置RaiseError
my $dbh = DBI -> connect ($dsn,$user_name,$password,{RaiseError->1});
处理错误
????在dump_members 调用connect( )方法时,应该启用RaiseError 错误处理属性,以便这些错误用一条错误消息就能自动地终止相应的脚本。也可以用其他方式处理这些错误。例如,可以自己检查错误而不必使用DBI。
????为了查看如何控制DBI 的错误处理行为,我们来仔细查看一下connect( ) 调用的最终参数。下面两个相关的属性是RaiseError 和P r i n t E r r o r:
????如果启用R a i s e E r r o r(设为非零值),如果在DBI 方法中出现错误,则DBI 调用die( ) 来显示一条消息并且退出。
????如果启用P r i n t E r r o r,在出现DBI错误时,DBI 会调用warn( ) 来显示一条消息,但是相应脚本会继续执行。
????缺省时, RaiseError 是禁用的,而PrintError 启用。在此情况下,如果connect( )调用失败,则DBI 显示一条消息,而且继续执行。这样,如果省略connect( ) 的四个参数,则得到缺省的错误处理行为,可以如下检查错误:
????$dbh=DBI->connect($dsn,$password) or exit (1);
????如果出现错误,则connect( ) 返回undef 表示失败,并且触发对exit( ) 的调用。因为DBI 已经显示了错误消息,所以您就不一定要显示它了。
????如果明确给出该错误检查属性的缺省值,可如下调用connect( )。
????$dbh=DBI->connect($dsn,{RaiseError=>0,PrintError=>1})
????or exit (1);
????这就需要更多的编写工作,但是即使对不经意的读者,处理错误行为也会更为明显。
????如果想自己检查错误,并显示自己的消息,应该禁用RaiseError 和P r i n t E r r o r:

????变量$DBI::err 和$ DBI : :er r s t r,只用于所显示的die( ) 调用中,有助于构造错误消息。它们含有MySQL错误代码和错误字符串,非常像C API 函数中的mysql_errno( ) 和mysql_error( )。
????如果仅仅要DBI 处理错误,以便不必自己检查它们,则启用R a i s e E r r o r:
????$dbh=DBI->connect ($dsn,{RaiseError=>1});
????到目前为止,这是最容易的方法,并且是dump_members 带来的。如果在脚本退出时,想要执行某种类型的清除代码,启用RaiseError 可能是不恰当的,尽管在这种情况下,可以重新定义$SIG{_DIE_} 句柄,可以做想做的事情。
????避免启用RaiseError 属性的另一个原因是DBI 在它的消息中显示技术信息,如下:
????disconnect(DBI::db=HASH(0x197aae4)invalidates 1active statement.Either
????destroy statement handles or call finish on them before disconnecting.
????对于编程者来说,这是好的信息,但对普通用户可能没有什么意义。在此情形,最好自己检查错误,以便可以显示对期望使用这个脚本的人更有意义的消息。或者也可在这里考虑重新定义$SIG{_DIE_} 句柄。这样可能很有用,因为它允许启用RaiseError 来使错误处理简单化,而不是用自己的消息替换DBI 给出的缺省错误消息。
下面有一个对数据操作过程中做异常处理记录的代码 :
sub export_data
{
my ($table_name,$sql_select,$insert_columns,$columns_count,$column_types);
eval{
my $startTime=time;
($table_name,$column_types)=@_;
my $dbh_mssql=DBI->connect("dbi:ODBC:$source_name",$source_user_name,$source_user_psd,{RaiseError =>1});
$dbh_mssql->{LongTruncOk}=1;
$dbh_mssql->{LongReadLen}=1048576;
my $sth_select=$dbh_mssql->prepare($sql_select);
$sth_select->execute() or die "Cannot execute: ". $sth_select->errstr();
##生成标识ID
my $gid=rand(3200);
my $data_str="";
my $select_data;
while($select_data=$sth_select->fetchrow_arrayref())
{
if($data_str ne "")
{
$data_str="$data_str,";
}
$data_str=$data_str."[$gid,'+',['".join("','",@{$select_data})."']]";
}
printf("读出时间%.1f seconds.n",time-$startTime);
$sth_select->finish;
$dbh_mssql->disconnect;
$startTime=time;
#open(FILE,">>all_export_data_fre.txt");
#syswrite(FILE,"$data_strn");
#close(FILE);
$data_str=encode("utf8",decode("gbk",$data_str));
#open(FILE,">>all_export_data.txt");
#syswrite(FILE,"$data_strn");
#close(FILE);
##测试的时候,查看数据的语句。
# print "n",$data_str,"n";
if($data_str ne "")
{
$data_str="[$data_str]";
my $args = { host => $aim_ip,port => $hs_port };
my $hs = new Net::HandlerSocket($args);
my $res = $hs->open_index($gid,$aim_db_name,$table_name,'PRIMARY',"$insert_columns");
die $hs->get_error() if $res != 0;
##这里不加EVAL不行的,不信?您试
$res = $hs->execute_multi(eval($data_str));
die $hs->get_error() if $hs->get_error() != 0;
$hs->close();
}
undef $data_str;
printf("写入时间%.1f seconds.n",time-$startTime);
};
print "An error occurred: !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!" if $@;
if($@)
{
open(FILE,">>$logname");
syswrite(FILE,"$nn");
syswrite(FILE,"$table_namen");
syswrite(FILE,"$sql_selectn");
syswrite(FILE,"$@n");
($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst) = localtime(time());
$format_time=sprintf("%d-%d-%d %d:%d:%d",$year+1990,$mon+1,$sec);
syswrite(FILE,$format_time."n");
close(FILE);
open(FILE,">>$repairname");
syswrite(FILE,"$table_name::$sql_select::");
close(FILE);
}
print "An error occurred: $@" if $@;
}