目錄

名稱

perldebtut - 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;',強制每個變數在使用前宣告。

現在當你執行時,perl 會抱怨 3 個未宣告的變數,而且我們會收到四個錯誤訊息,因為有一個變數被參照兩次

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 "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 除錯器。它會在第一行可執行程式碼停止,並等待輸入。

在我們繼續之前,你會想要知道如何退出除錯器:只使用字母「q」,而不是「quit」或「exit」這些字詞

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」和同類項要求你不使用類型識別碼($@%),僅使用「名稱」

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'     

那沒有什麼幫助,裡面有幾個歡迎,但沒有指出哪些是鍵,哪些是值,它只是一個列出的陣列傾印,在本例中,沒有特別有幫助。這裡的訣竅是使用資料結構的參考

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

參考值真的已經傾印出來,我們終於可以看看我們在處理什麼了。我們的引號完全有效,但對於我們的目的來說是錯誤的,因為「and jerry」被視為 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」指令,它真的很有用,可以愉快地傾印出巢狀參考值、完整的物件、部分物件,幾乎是你丟給它的任何東西

我們來建立一個快速的物件並執行 x-plode,首先我們會啟動除錯器:它想要從 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>

很有用,對吧?你幾乎可以在那裡評估任何東西,並使用程式碼或正規表示式片段進行實驗,直到牛群回家

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
 use v5.36;

 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 的佔位符

動作、監控變數、堆疊追蹤等:在待辦事項清單中。

a 

w 

t 

T

正規表示式

是否曾經想知道正規表示式是什麼樣子?你將需要使用 DEBUGGING 旗標編譯 perl

> 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$'

你真的想知道嗎?:-) 若要取得更多關於讓正規表示式運作的詳細資訊,請查看 perlreperlretut,並查看 perldebguts,以解碼神秘標籤(例如上述的 BOL 和 CURLYN)。

輸出提示

若要從錯誤記錄取得所有輸出,並透過有用的作業系統緩衝區不遺漏任何訊息,請在指令碼開頭插入類似這樣的行

$|=1;	

若要監控動態成長的記錄檔的尾端(從命令列)

tail -f $error_log

將所有 die 呼叫包裝在處理常式中可能有助於查看它們是如何以及從何處被呼叫的,perlvar 有更多資訊

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

perlopentutperlfaq8 中說明了 STDOUT 和 STDERR 檔案控制項重新導向的各種有用技術。

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 除錯器 perl -d scriptname,以使用 px 指令從 perl 除錯器內部檢查資料。你可以逐步執行程式碼,使用 b 設定中斷點,並使用 sn 逐步執行該程式碼,使用 c 繼續執行,並使用 r 從子程式返回。當你深入了解時,這些東西相當直觀。

當然還有很多東西可以了解,這只是粗淺的介紹。進一步學習的最佳方式是使用 perldoc 了解語言的更多資訊,閱讀線上說明 (perldebug 可能會是下一個要去的地方),當然,還有實驗。

另請參閱

perldebugperldebgutsperl5db.plperldiagperlrun

作者

Richard Foley <richard.foley@rfi.net> 版權所有 (c) 2000

貢獻者

許多人提供了有用的建議和貢獻,特別是

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

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

Peter Scott <Peter@PSDT.com>