1. ホーム
  2. パール

perl の例外処理 die, warn, eval 関数 - DBI の RaiseError

2022-03-15 02:01:17

    例えば、存在しないファイルを開こうとしたり、まだファイルがあるディレクトリを削除したり、読み取り権限のないファイルを読み取ろうとした場合など、多くの場合、システムコールは失敗する可能性があります。これまでの例でdie関数を使用しましたが、このセクションでは、エラー処理とエラー処理関数について詳しく説明します。die関数、warn関数、eval関数などです。

die関数は、コマンドやファイルハンドルに失敗した場合、Perlスクリプトを終了するために使用します。

warn関数はdie関数と似ていますが、スクリプトを終了させません。

eval関数はいろいろな使い方がありますが、やはり例外処理に使われることが多いようです。

読者は短絡演算子 && と || を覚えていると思いますが、これはまず左側のオペランドの値を求めてから右側のオペランドの値を求めます。もし&&の左側のオペランドの値が真であれば、その右側のオペランドが評価される。|| の左側のオペランドの値が偽の場合、その右側のオペランドのみを解決する。

Carp.pmモジュールです。ユーザが利用できるスクリプトを終了する方法はいろいろありますが、Perl 5では拡張されたdieとwarnの機能を持つCarpモジュールが提供されています。(詳しくは例12.10を参照してください)。

18.4.1 die関数

システムコールに失敗した場合、die関数はSTDERRに文字列を出力し、現在の値$! でスクリプトを終了します。変数$! には、システム・エラーを示す数値を格納したUNIXグローバル変数である errnoの現在値が格納される。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)ダイ LISTdie

例 18.66

(スクリプト内)1 die "ジャンクにcdできない: $! \n" unless chdir "/usr/bin/junk"; (Output)1 Can't cd to junk: そのようなファイルやディレクトリはありません

説明

1. chdir の呼び出しに失敗しました。errnoのエラーメッセージを含む$! 改行により、die関数に続く文字列が表示され、その中に変数$! の値が含まれています。

例 18.67

(In Script)1 die unless chdir '/plop' ;(Output)1 croak.perl line 4で死亡しました。

説明

1. chdirの呼び出しに失敗しました。今回は$!がdie文字列に入っていないので、エラーが発生した行を表示してください。

例 18.68

(スクリプト内)1 chdir '/plop' or die "Stopped";(Output)1 croak.perl line 4で停止しました。

説明

1. この例の出力は前の例と同じですが、異なる構文が使用されています。chdirの呼び出しに失敗した場合、orの右側にあるdie関数が実行されます。

warn関数

warn関数(演算子)はdieとほぼ同じですが、前者はプログラムの実行を継続させるという点が異なります。die関数がevalブロックの中で呼ばれた場合、dieに渡された引数の文字列は、特殊変数$@にも代入されます。dieを呼び出した後、この変数はwarn関数の引数として渡すことができ、その出力はSTDERRに送られます(ecal関数のセクションを参照してください)。

eval関数

eval関数は例外処理、つまりエラーをキャッチするために使用されます。eval の後に続くステートメントブロックは別の Perl プログラムとして扱われ、パースされますが、その変数設定、サブルーチン、フォーマット定義はすべて eval が実行されるまで保持されます。

eval関数が返す値は、直前の式の値です。コンパイルエラーやランタイムエラーが発生した場合、あるいは die 文が実行された場合は、未定義値が返され、特殊変数 $@ にエラーメッセージの内容が設定されます。エラーが発生しない場合、$@は空文字列になります。

eval を使って Perl 式の値を見つける

例 18.69

(スクリプト)#! /bin/perl# eval関数は入力された各行を評価し# 、その結果を返します。あたかも、独立した小さなPerlスクリプトを実行しているよう# なものです。スクリプト名 plsh1 print "> "; # プロンプトを表示2 while(<STDIN>){3 $result=eval ; # eval は式 $_4 を評価します warn $@ if $@; # もしエラーが発生したら toprint " に代入されます。 $resultn if $result";6 print "> "; # Print the prompt}(Output)(The Command line)$ plsh2 > hello5 hello2 > bye5 bye2 > 5 + 45 92 > 8 / 35 2. 66666666666666672 > 5 / 04 ゼロによる不正な除算 at (eval 5) line 3, <STDIN> ; line 5.> "Oh I seeCan't find string terminator '"' before EOF at (eval 6) line 1, <STDIN> line> exit.

説明

1. この行は、ユーザーに対してプロンプトメッセージを表示します。このプログラムは小さなPerlのシェルに似ています。特にPerlが式をどのように扱うかわからない場合、プログラムに入れる前に、式がどの程度良いかをユーザーがチェックするのに役立ちます。

2. whileループに入る。ループに入るたびに、ユーザーからの入力行を読み取り、それを$_に代入します。

3.引数なしのevalは、$_の式を評価し、その結果を$resultに代入します。

4. evalは、評価された式に起因する構文エラーまたはシステムエラーを発見した場合、返されたエラーメッセージを変数$@に代入します。エラーが見つからなかった場合は、$@に空文字列が代入されます。

5. 5. 式が正常に評価された場合、その結果を表示する。

6. プロンプトメッセージを表示し、再度ループに入る。

eval を使ってプログラムのエラーを検出する

例 18.70

(スクリプト内)#! /bin/perlprint "数を教えてください.";chop($a=<STDIN>); print "割るものを教えてください.";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.45. 45.00を6.00で割ると7.50.4 私は今ここにいます。除数をください.02 Illegal division by zero at . /eval.p line 8, <STDIN> line 2.4 I'm here now.

説明

1. eval関数は除算($a/$b)を計算し、その結果を$answerに保存します。なお、$answerはevalの中で最初に使用されなければならず、evalの実行が終わるまで保持されます。

2. 2. すべてがうまくいき、除算操作が正常に終了した場合、この行は無視されます。もしエラーが見つかった場合(例えば、0で割った場合)、$@変数にシステムエラーメッセージが設定され、そのメッセージがwarn関数を通してSTDERRに出力され、プログラムの実行が再開されます。evalブロックの中でdie関数が呼ばれた場合は、プログラムは終了せず、evalブロックを抜けた後も実行を継続します。

3. 実行に成功した場合、除算演算の結果が表示される。

4. この行は、警告関数がスクリプトを終了させないため、失敗してもプログラムが継続されることを示すためだけに出力されています。

eval関数とここでの説明

例 18.71

(スクリプト)#! /bin/perl1 eval<<"EOF";2 chdir "joker" || die "Can't cd: $! \n";3 EOF4 print "The error message from die:print "プログラム $0 still in progress.\n";(Output)4 dieのエラーメッセージです。Can't cd: no such file or directory5 プログラム. /eval4.pはまだ進行中です。

説明

1. ここでの文書は、特別な形式の参照に似ています。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( )呼び出しの最終パラメータを詳しく見てみましょう。次の2つの関連するプロパティは、RaiseErrorとP r i n t E r o r です。

    R a i s e E r o r が有効(0以外の値に設定)であれば、DBIメソッドでエラーが発生した場合、DBIはdie( )を呼び出してメッセージを表示し、終了します。

    P r i n t E r o r を有効にすると、DBIエラーが発生した場合、DBIはwarn()を呼び出してメッセージを表示しますが、対応するスクリプトは実行を継続します。

    デフォルトでは、RaiseErrorは無効、PrintErrorは有効になっています。この場合、connect( )呼び出しが失敗すると、DBIはメッセージを表示し、実行は継続されます。したがって、connect( )への4つの引数を省略すると、デフォルトのエラー処理動作になり、以下のようにエラーをチェックすることができるようになります。

    $dbh=DBI->connect($dsn,$user_name,$password) or exit (1);



    エ ラ ーが発生す る と 、 connect( ) は失敗 し た と き に undef を返し、 exit( ) を呼び出すきっかけにな り ます。DBIはすでにエラーメッセージを表示しているので、必ずしも表示する必要はありません。

    このエラーチェックのプロパティに明示的にデフォルト値を与えると、次のようにconnect()を呼び出すことができます。

$dbh=DBI->connect($dsn,$user_name,$password,{RaiseError=>0,PrintError=>1})


    or exit (1);



    その分、文章量は増えますが、誤動作の対処が素人目にもわかりやすくなります。

    自分でエラーチェックを行い、独自のメッセージを表示したい場合は、RaiseError と P r i n t E r o r を無効化する必要があります。



    変数$DBI::errと$ DBI : :er r s t rはdie()呼び出しでのみ使用され、エラーメッセージを作成するのに役立ちます。これらは、C API 関数の mysql_errno( ) および mysql_error( ) と同様に、MySQL エラーコードとエラー文字列を含んでいます。

    もし、DBIにエラーを処理させて、自分でチェックする必要がないようにしたい場合は、R a i s e E r o r を有効にします。

    $dbh=DBI->connect ($dsn,$user_name,$password,{RaiseError=>1});



    これは、圧倒的に簡単な方法で、dump_membersによってもたらされます。スクリプトが終了するときに何らかのクリーンアップコードを実行したい場合、RaiseError を有効にすることは適切でないかもしれませんが、その場合、$SIG{_DIE_} ハンドルを再定義してやりたいことをできるようにすることができます。

    RaiseError属性を有効にしないもう1つの理由は、以下のようにDBIがメッセージに技術情報を表示するためです。

    disconnect(DBI::db=HASH(0x197aae4)invalidates 1active statement.Either


    destroy statement handles or call finish on them before disconnecting.



    これはプログラマーにとっては良い情報ですが、一般のユーザーにとってはあまり意味のない情報かもしれません。この場合、自分でエラーをチェックして、このスクリプトを使うことを予期している人にとってより意味のあるメッセージを表示できるようにするのが最善である。あるいは、ここで $SIG{_DIE_} ハンドルを再定義することを検討してください。これは、DBIによって与えられたデフォルトのエラーメッセージを独自のメッセージで置き換えるのではなく、エラー処理を簡単にするためにRaiseErrorを有効にすることができるので、便利かもしれません。

以下は、データ操作中に例外ロギングを行うコードです。

sub export_data
 {
 my ($table_name,$sql_select,$insert_columns,$columns_count,$column_types);
   eval{
         my $startTime=time;
        ($table_name,$sql_select,$insert_columns,$columns_count,$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();
      
         ## Generate the identification 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("Read out time %.1f seconds.\n",time-$startTime);

$sth_select->finish;
$dbh_mssql->disconnect;

         $startTime=time;
#open(FILE,">>all_export_data_fre.txt");    
#syswrite(FILE,"$data_str\n");    
#close(FILE); 
 
   $data_str=encode("utf8",decode("gbk",$data_str));
#open(FILE,">>all_export_data.txt");    
#syswrite(FILE,"$data_str\n");    
#close(FILE); 
 
          ## Statements to view data when testing.
     #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;		

			  
             ## It doesn't work here without EVAL, don't believe me? You try
			 $res = $hs->execute_multi(eval($data_str));
         
 
             die $hs->get_error() if $hs->get_error() ! = 0;
             $hs->close();
         }
        undef $data_str;
         printf("write time %.1f seconds.\n",time-$startTime);
		 };
		
		  print "An error occurred: !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! " if $@;
if($@)
{
 open(FILE,">>$logname");  
syswrite(FILE,"$n\n");