NAME

perldebtut - Perl でのデバッグのチュートリアル

DESCRIPTION

perl デバッガの使い方の(とても) 軽量な紹介および、 perl プログラムの デバッグに関する、すでに存在するより深い情報源へのポインタです。

perl を毎日使っているのに、perl デバッガを使うことについて何も知らないように 思われる人が非常にたくさんいます。 これはそのような人たちのためのものです。

use strict

まず最初に、perl のプログラムをデバッグするときに、デバッガを全く 使うことなく、人生を遥かに素直なものにするためにできることがいくつか あります。 それを示すために、"hello" という名前の、単純ですが問題を抱えたスクリプトを 示します:

        #!/usr/bin/perl

        $var1 = 'Hello World'; # always wanted to do that :-)
        $var2 = "$varl\n";

        print $var2; 
        exit;

これはエラーなくコンパイルおよび実行されますが、おそらく想定したことは 起きないでしょう; すなわち、"Hello World\n" とは全く表示されません; 一方 (コンピュータに少しある傾向通りに) するように言われた通りに 動作しています。 これは、改行文字を表示していて、それが空行のように見えるのです。 2 つの変数があるように見えますが、実際には (タイプミスのために) 3 つの変数があるのです:

        $var1 = 'Hello World';
        $varl = undef;
        $var2 = "\n";

この種の問題を補足するには、スクリプトの最初の行の後に 'use strict;' を 書いて strict モジュールを導入することで、変数を使う前には宣言することを 強制できます。

これで実行すると、perl は 3 つの未宣言変数に関して 4 つのエラーメッセージが でます; なぜなら 1 つの変数は 2 回参照されているからです:

 Global symbol "$var1" requires explicit package name at ./t1 line 4.
 Global symbol "$var2" requires explicit package name at ./t1 line 5.
 Global symbol "$varl" requires explicit package name at ./t1 line 5.
 Global symbol "$var2" requires explicit package name at ./t1 line 7.
 Execution of ./hello aborted due to compilation errors.     

バッチリだ! そしてこれを修正するために、全ての変数を明示的に宣言することにすると、 スクリプトは以下のようになります:

        #!/usr/bin/perl
        use strict;

        my $var1 = 'Hello World';
        my $varl = undef;
        my $var2 = "$varl\n";

        print $var2; 
        exit;

それから、もう一度実行する前に文法チェックを行います(これは常に いい考えです):

        > perl -c hello
        hello syntax OK 

そして実行すると、やはり "\n" が表示されますが、少なくともなぜかは 分かります。 コンパイルしたスクリプトに '$varl' (文字 'l' です) があることが明らかになり、 単に $varl を $var1 に変更すれば問題は解決します。

データの見方と -w と v

よし、でも本当に、動的変数に入っているデータを、それを使う直前に知るには?

        #!/usr/bin/perl 
        use strict;

        my $key = 'welcome';
        my %data = (
                'this' => qw(that), 
                'tom' => qw(and jerry),
                'welcome' => q(Hello World),
                'zip' => q(welcome),
        );
        my @data = keys %data;

        print "$data{$key}\n";
        exit;                               

良さそうに見えます; 文法チェック (perl -c scriptname) の後、実行してみると、 またも空行しか出ません! ふーむ。

ここで一般的なデバッグ手法の一つは、print 文を自由にいくつかばらまいて、 データをプリントする直前のチェックを追加することです:

        print "All OK\n" if grep($key, keys %data);
        print "$data{$key}\n";
        print "done: '$data{$key}'\n";

そして再挑戦します:

        > perl data
        All OK     

        done: ''

同じコード片を見つめすぎて、木を見て森を見ずになっていることがあります; 一服して違う手法を試しましょう。 それは、コマンドラインで perl に '-d' オプションを与えることで騎兵隊を 迎え入れることです:

        > perl -d data 
        Default die handler restored.

        Loading DB routines from perl5db.pl version 1.07
        Editor support available.

        Enter h or `h h' for help, or `man perldebug' for more help.

        main::(./data:4):     my $key = 'welcome';   

ここでしたことは、スクリプトに対して組み込み perl デバッガを 起動したことです。 それは実行コードの最初の行で停止して、入力を待っています。

先に進む前に、どうやってデバッガを抜けるかを知りたいでしょう: 単語 'quit' や 'exit' ではなく、単に文字 'q' をタイプしてください:

        DB<1> q
        >

これで、再びホームグラウンドに戻ってきます。

ヘルプ

スクリプトに対してもう一度デバッガを起動して、ヘルプメニューを見てみます。 ヘルプを呼び出すには複数の方法があります: 単純な 'h' はヘルプリストの 要約を出力し、'|h' (パイプ-h) はヘルプをページャ(多分 'more' か 'less') に送り、最後に 'h h' (h-空白-h) はヘルプスクリーン全体を 表示します。 以下は要約ページです:

D1h

 List/search source lines:               Control script execution:
  l [ln|sub]  List source code            T           Stack trace
  - or .      List previous/current line  s [expr]    Single step [in expr]
  v [line]    View around line            n [expr]    Next, steps over subs
  f filename  View source in file         <CR/Enter>  Repeat last n or s
  /pattern/ ?patt?   Search forw/backw    r           Return from subroutine
  M           Show module versions        c [ln|sub]  Continue until position
 Debugger controls:                       L           List break/watch/actions
  o [...]     Set debugger options        t [expr]    Toggle trace [trace expr]
  <[<]|{[{]|>[>] [cmd] Do pre/post-prompt b [ln|event|sub] [cnd] Set breakpoint
  ! [N|pat]   Redo a previous command     B ln|*      Delete a/all breakpoints
  H [-num]    Display last num commands   a [ln] cmd  Do cmd before line
  = [a val]   Define/list an alias        A ln|*      Delete a/all actions
  h [db_cmd]  Get help on command         w expr      Add a watch expression
  h h         Complete help page          W expr|*    Delete a/all watch exprs
  |[|]db_cmd  Send output to pager        ![!] syscmd Run cmd in a subprocess
  q or ^D     Quit                        R           Attempt a restart
 Data Examination:     expr     Execute perl code, also see: s,n,t expr
  x|m expr       Evals expr in list context, dumps the result or lists methods.
  p expr         Print expression (uses script's current package).
  S [[!]pat]     List subroutine names [not] matching pattern
  V [Pk [Vars]]  List Variables in Package.  Vars can be ~pattern or !pattern.
  X [Vars]       Same as "V current_package [Vars]".
  y [n [Vars]]   List lexicals in higher scope <n>.  Vars same as V.
 For more help, type h cmd_letter, or run man perldebug for all docs. 

とても多くの混乱させるオプションがあります! これは見た目ほど悪くはありませんし、これらすべてについてもっと知ることは とても有用ですし、楽しくもあります!

まず知っておくべきいくつかのコマンドがあります。 この時点では何かのライブラリを使っているとは考えていないでしょうが、 'M' は現在読み込まれているモジュールとバージョン番号を表示し、 一方 'm' はメソッドを表示し、'S' は以下のように、(パターンによって) 全てのサブルーチンを表示します。 'V' と'X' は、パッケージスコープと、パターンによって制限できる、 変数を表示します。

        DB<2>S str 
        dumpvar::stringify
        strict::bits
        strict::import
        strict::unimport  

X' とその親類を使う時には、型指定子($@%)を使う必要はありません; 単に 'name' を入力してください:

        DM<3>X ~err
        FileHandle(stderr) => fileno(2)    

問題を抱えた小さなプログラムがあって、今どこにいるか、そしてデータが どのようにあっているのかを見ようとしていることを思い出してください。 まず最初に、現在位置 (この場合ではコードの最初の行) のコードを見てみましょう; 'v' を使います:

        DB<4> v
        1       #!/usr/bin/perl
        2:      use strict;
        3
        4==>    my $key = 'welcome';
        5:      my %data = (
        6               'this' => qw(that),
        7               'tom' => qw(and jerry),
        8               'welcome' => q(Hello World),
        9               'zip' => q(welcome),
        10      );                                 

行番号 4 にあるのは助けになるポインタで、今どこにいるのかを示しています。 さらにコードを見るには、再び 'v' をタイプします:

        DB<4> v
        8               'welcome' => q(Hello World),
        9               'zip' => q(welcome),
        10      );
        11:     my @data = keys %data;
        12:     print "All OK\n" if grep($key, keys %data);
        13:     print "$data{$key}\n";
        14:     print "done: '$data{$key}'\n";
        15:     exit;      

そしてもし行番号 5 を再び見たいなら、'l 5' をタイプします (空白に注意してください):

        DB<4> l 5
        5:      my %data = (

この場合、見られるものはあまり多くはありませんが、もちろん普通は見渡すのに 何ページにもなる内容があるので、'l' はとても有用です。 見ている場所を実行しようとしているところにリセットするには、単一の '.' を タイプします:

        DB<5> .
        main::(./data_a:4):     my $key = 'welcome';  

表示されている行は 次に 実行されようとしているもので、まだ 実行されていません。 従って、ここで文字 'p' を使って変数を表示できますが、この時点では 表示されるのは空(未定義)値だけです。 するべきことは、's' を使って次の実行可能文に進むことです:

        DB<6> s
        main::(./data_a:5):     my %data = (
        main::(./data_a:6):             'this' => qw(that),
        main::(./data_a:7):             'tom' => qw(and jerry),
        main::(./data_a:8):             'welcome' => q(Hello World),
        main::(./data_a:9):             'zip' => q(welcome),
        main::(./data_a:10):    );   

ここで最初の ($key) 変数を見ることができます:

        DB<7> p $key 
        welcome 

行 13 が処理の場所なので、文字 'c' を使って、今度は「一回だけ」の ブレークポイントを与えられた行かサブルーチンに挿入することでそこまで 進めていきましょう:

        DB<8> c 13
        All OK
        main::(./data_a:13):    print "$data{$key}\n";

チェック('All OK' が表示された場所)を通り過ぎて、作業の要点の直線で 停止しました。 何が起きているのかを見るために二つの変数を表示させようとすることが できます:

        DB<9> p $data{$key}

あまりありませんが、ハッシュを見てみましょう:

        DB<10> p %data
        Hello Worldziptomandwelcomejerrywelcomethisthat 

        DB<11> p keys %data
        Hello Worldtomwelcomejerrythis  

うーん、これはとても読みやすいというものではありません; そして親切な マニュアル (h h) を使うと、'x' コマンドが見込みがありそうです:

        DB<12> x %data
        0  'Hello World'
        1  'zip'
        2  'tom'
        3  'and'
        4  'welcome'
        5  undef
        6  'jerry'
        7  'welcome'
        8  'this'
        9  'that'     

これはあまり助けにはなりません; ここには 2 つの "welcome" がありますが、 どれがキーでどれが値かを示すものがなく、単に配列ダンプの一覧で、 この場合、特に役に立つものではありません。 ここでの技は、データ構造への リファレンス を使うことです:

        DB<13> x \%data
        0  HASH(0x8194bc4)
           'Hello World' => 'zip'
           'jerry' => 'welcome'
           'this' => 'that'
           'tom' => 'and'
           'welcome' => undef  

リファレンスが完全にダンプされて、ついに扱っているものが見えました。 クォートは完全に有効でしたが、今回の目的には間違ったものでした; 'and jerry' が熟語ではなく、2 つの別々の単語として扱われています; 従って、2 つ組のハッシュ構造のアライメントがずれたのです。

'-w' オプションはこれについて教えてくれるので、最初に使っておけば、 多くの問題から救ってくれていました:

        > perl -w data
        Odd number of elements in hash assignment at ./data line 5.    

クォートを修正します: 'tom' => q(and jerry)、そして再実行すると、今度は 予想通りの出力が得られます:

        > perl -w data
        Hello World

ここにいる間に 'x' コマンドをより近くで見てみると、これは本当に有用で、 ネストしたリファレンス、完全なオブジェクト、オブジェクトの一部 - コマンドに 与えたものは何でも - 楽しくダンプします:

簡単なオブジェクトを作って、見てみましょう; まずデバッガを起動します: これは STDIN から何らかの形の入力を要求するので、何か無害なもの - ゼロ - を 入力します:

        > perl -de 0
        Default die handler restored.

        Loading DB routines from perl5db.pl version 1.07
        Editor support available.

        Enter h or `h h' for help, or `man perldebug' for more help.

        main::(-e:1):   0                       

ここで、複数行を使ってその場でオブジェクトを構築します (バックスラッシュに注意してください):

        DB<1> $obj = bless({'unique_id'=>'123', 'attr'=> \
        cont:   {'col' => 'black', 'things' => [qw(this that etc)]}}, 'MY_class')

そして見てみましょう:

        DB<2> x $obj
        0  MY_class=HASH(0x828ad98)
                'attr' => HASH(0x828ad68)
        'col' => 'black'
        'things' => ARRAY(0x828abb8)
                0  'this'
                1  'that'
                2  'etc'
                'unique_id' => 123       
        DB<3>

便利でしょう? ここでほとんどなんでも eval できて、ちょっとしたコードや正規表現を いつまでも実験できます。

        DB<3> @data = qw(this that the other atheism leather theory scythe)

        DB<4> p 'saw -> '.($cnt += map { print "\t:\t$_\n" } grep(/the/, sort @data))
        atheism
        leather
        other
        scythe
        the
        theory  
        saw -> 6

コマンド履歴を見たいなら、'H' をタイプします:

        DB<5> H
        4: p 'saw -> '.($cnt += map { print "\t:\t$_\n" } grep(/the/, sort @data))
        3: @data = qw(this that the other atheism leather theory scythe)
        2: x $obj
        1: $obj = bless({'unique_id'=>'123', 'attr'=>
        {'col' => 'black', 'things' => [qw(this that etc)]}}, 'MY_class')
        DB<5>

以前に使ったコマンドを繰り返したい場合は、感嘆符を使います: '!':

        DB<5> !4
        p 'saw -> '.($cnt += map { print "$_\n" } grep(/the/, sort @data))
        atheism
        leather
        other
        scythe
        the
        theory  
        saw -> 12

リファレンスについてのさらなる情報については perlrefperlreftut を 参照してください。

コードをステップ実行する

以下は摂氏と華氏とを変換する単純なプログラムで、やはり問題を抱えています:

        #!/usr/bin/perl -w
        use strict;

        my $arg = $ARGV[0] || '-c20';

        if ($arg =~ /^\-(c|f)((\-|\+)*\d+(\.\d+)*)$/) {
                my ($deg, $num) = ($1, $2);
                my ($in, $out) = ($num, $num);
                if ($deg eq 'c') {
                        $deg = 'f';
                        $out = &c2f($num);
                } else {
                        $deg = 'c';
                        $out = &f2c($num);
                }
                $out = sprintf('%0.2f', $out);
                $out =~ s/^((\-|\+)*\d+)\.0+$/$1/;
                print "$out $deg\n";
        } else {
                print "Usage: $0 -[c|f] num\n";
        }
        exit;

        sub f2c {
                my $f = shift;
                my $c = 5 * $f - 32 / 9;
                return $c;
        }

        sub c2f {
                my $c = shift;
                my $f = 9 * $c / 5 + 32;
                return $f;
        }

なぜか、華氏から摂氏への変換は推測される結果を返すのに失敗します。 以下はどうなるかです:

        > temp -c0.72
        33.30 f

        > temp -f33.3
        162.94 c

全く一貫していません! 手動でコードにブレークポイントをセットして、何が起きているかを見るために デバッガで実行してみます。 ブレークポイントは、デバッガを中断なしで実行するためのフラグで、 ブレークポイントに到達すると、実行を停止してさらなる対話のためにプロンプトを 出します。 通常の使用では、これらのデバッガコマンドは完全に無視され、これらは 製品コードに残しても安全です - すこし乱雑かもしれませんが。

        my ($in, $out) = ($num, $num);
        $DB::single=2; # insert at line 9!
        if ($deg eq 'c') 
                ...

        > perl -d temp -f33.3
        Default die handler restored.

        Loading DB routines from perl5db.pl version 1.07
        Editor support available.

        Enter h or `h h' for help, or `man perldebug' for more help.

        main::(temp:4): my $arg = $ARGV[0] || '-c100';     

'c' をタイプして、単純に予めセットされたブレークポイントまで続けます:

        DB<1> c
        main::(temp:10):                if ($deg eq 'c') {   

引き続いて表示コマンドで今どこにいるかを見ます:

        DB<1> v
        7:              my ($deg, $num) = ($1, $2);
        8:              my ($in, $out) = ($num, $num);
        9:              $DB::single=2;
        10==>           if ($deg eq 'c') {
        11:                     $deg = 'f';
        12:                     $out = &c2f($num);
        13              } else {
        14:                     $deg = 'c';
        15:                     $out = &f2c($num);
        16              }                             

そして今使っている値を表示させます:

        DB<1> p $deg, $num
        f33.3

コロンの付いているどの行にも別のブレークポイントを置くことができます; サブルーチンから返ってきたばかりのところである 17 行目を使うことにして、 あとからここで一旦停止したいとします:

        DB<2> b 17

これに対する反応はありませんが、一覧 'L' コマンドを使うことで、どの ブレークポイントがセットされているかを見ることができます:

        DB<3> L
        temp:
                17:            print "$out $deg\n";
                break if (1)     

ブレークポイントを削除するためには 'B' を使うことに注意してください。

ここでサブルーチンの中に入っていくことにします; 今回は行番号ではなく、 サブルーチン名を使います; その後、今となってはおなじみの 'v' を使います:

        DB<3> c f2c
        main::f2c(temp:30):             my $f = shift;  

        DB<4> v
        24:     exit;
        25
        26      sub f2c {
        27==>           my $f = shift;
        28:             my $c = 5 * $f - 32 / 9; 
        29:             return $c;
        30      }
        31
        32      sub c2f {
        33:             my $c = shift;   

ここと 29 行目との間にサブルーチンがあり、そこを シングルステップ したい 場合、's' コマンドも使えますし、サブルーチンは実行するけれども サブルーチン内部は検査しないという 'n' コマンドで ステップオーバーできます。 しかし、この場合には、単に 29 行まで進めていきます:

        DB<4> c 29  
        main::f2c(temp:29):             return $c;

そして返り値を見てみます:

        DB<5> p $c
        162.944444444444

これは全く間違った答えですが、合計は正しいように見えます。 演算子の優先順位が何かを行っているのでしょうか? 合計に関してその他の可能性を試してみます:

        DB<6> p (5 * $f - 32 / 9)
        162.944444444444

        DB<7> p 5 * $f - (32 / 9) 
        162.944444444444

        DB<8> p (5 * $f) - 32 / 9
        162.944444444444

        DB<9> p 5 * ($f - 32) / 9
        0.722222222222221

:-) これはより似ています! よし、ここで独自の返り値をセットして、'r' を使ってサブルーチンから返ります:

        DB<10> $c = 5 * ($f - 32) / 9

        DB<11> r
        scalar context return from main::f2c: 0.722222222222221

良さそうです; スクリプトの最後まで実行していましょう:

        DB<12> c
        0.72 c 
        Debugged program terminated.  Use q to quit or R to restart,
        use O inhibit_exit to avoid stopping after program termination,
        h q, h R or h O to get additional info.   

実際のプログラムの問題のある行に救急処置(不足していたかっこを挿入する)を 施して、終わりです。

a, w, t, T のためのプレースホルダ

アクション、変数の監視、スタックトレースなど: TODO リストです。

        a 

        w 

        t 

        T

正規表現

正規表現がどのように見えるか知りたいと思いましたか? 以下のようにするには perl を DEBUGGING フラグ付きでコンパイルする必要が あります:

        > perl -Dr -e '/^pe(a)*rl$/i'
        Compiling REx `^pe(a)*rl$'
        size 17 first at 2
        rarest char
         at 0
           1: BOL(2)
           2: EXACTF <pe>(4)
           4: CURLYN[1] {0,32767}(14)
           6:   NOTHING(8)
           8:   EXACTF <a>(0)
          12:   WHILEM(0)
          13: NOTHING(14)
          14: EXACTF <rl>(16)
          16: EOL(17)
          17: END(0)
        floating `'$ at 4..2147483647 (checking floating) stclass `EXACTF <pe>'
anchored(BOL) minlen 4
        Omitting $` $& $' support.

        EXECUTING...

        Freeing REx: `^pe(a)*rl$'  

本当に知りたかったですか? :-) 正規表現の動作に関する詳細については、perlre, perlretut を、 (上述の BOL や CURLYN などの)不思議なラベルを解読するには、 perldebguts を参照してください。

出力の小技

エラーログからの全ての出力を得て、親切な OS のバッファリングで メッセージを失わないようにするには、スクリプトの最初に以下のような行を 挿入してください:

        $|=1;   

動的に増え続けるログファイルの末尾を監視するには、(コマンドラインから):

        tail -f $error_log

全ての die 呼び出しをハンドラルーチンで囲むと、どこで、どのように 呼び出されているかを知るのに有用です; perlvar にさらなる情報があります:

        BEGIN { $SIG{__DIE__} = sub { require Carp; Carp::confess(@_) } }

STDOUT と STDERR ファイルハンドルのリダイレクトに関する様々な便利な テクニックが perlopentutperlfaq8 に記述されています。

CGI

「入力待ち」プロンプトからどうやれば逃れられるのかが分からない全ての CGI プログラマへの簡単なヒントとして、 CGI をコマンドラインから実行するときに、以下のようなものを試してください:

        > perl -d my_cgi.pl -nodebug 

もちろん CGIperlfaq9 にはもっと多くの情報があります。

GUI

コマンドラインインターフェースは emacs 拡張と密接に統合されていて、 vi インターフェースもあります。

しかし、これら全てをコマンドラインで実行する必要はありません; いくつかの GUI の選択肢もあります。 これらのよいところは、マウスカーソルを変数の上に移動させると適切な ウィンドウやバルーンにそのデータがダンプされ、もう 'x $varname' と タイプしなくていいことです :-)

特に以下のものの辺りを調べてみてください:

ptkdb ビルドインデバッガのための perlTK ベースのラッパー

ddd データ表示デバッガ

PerlDevKitPerlBuilder は NT 固有です

注意せよ。 (これらやその他のものに関するさらなる情報を頂ければ幸いです)。

まとめ

use strict-w を使ってどうやって良いコーディングを実践するかを 見ました。 perl -d scriptname とすることで perl デバッガを起動でき、デバッガの px のコマンドでデータを検査できます。 コードの中を通り抜けて、b でブレークポイントを設定し、 sn でステップ実行を行い、c で再開して、r サブルーチンから 戻ります。 うんざりしたときにはかなり直感的な機能です。

もちろんもっと多くの調べるべきことがありますが、これは表面を なぞっただけです。 より多くを学ぶための最善の方法は、言語に関してより多くを調べるために オンラインヘルプを読むために perldoc を使う(おそらく次に進むべき ところは perldebug でしょう)ことと、もちろん実践です。

SEE ALSO

perldebug, perldebguts, perldiag, perlrun

AUTHOR

Richard Foley <richard.foley@rfi.net> Copyright (c) 2000

貢献者

様々な人々が有益な提案や貢献をしてくれました; 特に:

Ronald J Kimball <rjk@linguist.dartmouth.edu>

Hugo van der Sanden <hv@crypt0.demon.co.uk>

Peter Scott <Peter@PSDT.com>