perldebtut - Perl 调试教程
一个(非常)轻量级的 Perl 调试器使用入门,以及指向有关调试 Perl 程序的现有更深入的信息源的指针。
有大量的人似乎对使用 Perl 调试器一无所知,尽管他们每天都在使用这种语言。这是为他们准备的。
首先,在调试 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";
为了捕获这种问题,我们可以通过引入 strict 模块来强制在使用之前声明每个变量,在脚本的第一行之后添加 '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 就可以解决问题。
好的,但是当你真的想查看你的数据,也就是在使用动态变量之前,它里面有什么呢?
#!/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 调试器。它已停止在第一行可执行代码处,并等待输入。
在我们继续之前,你需要知道如何退出调试器:只使用字母 '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”继续执行到该行,顺便说一下,“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”被视为两个单独的单词而不是一个短语,从而使配对的哈希结构失去对齐。
如果我们在开始时使用了“-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>
有用,对吧?你可以在里面评估几乎任何东西,并用代码片段或正则表达式进行实验,直到牛回家
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
有关引用的更多信息,请参阅 perlref 和 perlreftut
这是一个简单的程序,用于在摄氏度和华氏度之间进行转换,它也存在问题
#!/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
有没有想过正则表达式长什么样?你需要用包含 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$'
你真的想知道吗?:-) 想要了解更多关于正则表达式工作原理的细节,可以参考 perlre、perlretut,以及 perldebguts 来解读神秘的标签(如 BOL 和 CURLYN 等)。
为了获取错误日志中的所有输出,并且不会错过任何消息(通过有用的操作系统缓冲),在脚本开头插入类似这样的行
$|=1;
要观察动态增长的日志文件的尾部(从命令行),
tail -f $error_log
将所有 die 调用包装在处理程序例程中可以帮助你了解它们是如何以及从哪里被调用的,perlvar 提供了更多信息。
BEGIN { $SIG{__DIE__} = sub { require Carp; Carp::confess(@_) } }
在 perlopentut 和 perlfaq8 中解释了各种用于重定向 STDOUT 和 STDERR 文件句柄的有用技术。
这里给所有那些无法弄清楚如何在命令行运行 CGI 脚本时绕过“等待输入”提示的 CGI 程序员一个小提示,试试这样:
> perl -d my_cgi.pl -nodebug
命令行界面与 emacs 扩展紧密集成,并且也有一个 vi 界面。
不过,你并不需要在命令行上完成所有操作,有一些 GUI 选项可供选择。这些选项的优点在于你可以将鼠标悬停在变量上,变量数据的转储就会出现在相应的窗口中,或者在弹出气泡中,不再需要费力地输入 'x $varname' :-)
特别是,可以搜索以下内容:
ptkdb 基于 perlTK 的内置调试器包装器
ddd 数据显示调试器
PerlDevKit 和 PerlBuilder 是特定于 NT 的
注意:(希望有更多关于这些和其他工具的信息)。
我们已经了解了如何通过 use strict 和 -w 来鼓励良好的编码实践。我们可以运行 perl 调试器 perl -d scriptname,并使用 p 和 x 命令在 perl 调试器中检查数据。你可以逐步执行代码,使用 b 设置断点,并使用 s 或 n 逐步执行代码,使用 c 继续执行,使用 r 从子程序返回。当你真正开始使用它时,你会发现这些操作非常直观。
当然,还有很多东西要了解,这仅仅是触及了表面。学习更多知识的最佳方法是使用 perldoc 了解更多关于该语言的信息,阅读在线帮助(perldebug 可能是下一个去处),当然,还要进行实验。
perldebug,perldebguts,perl5db.pl,perldiag,perlrun
Richard Foley <[email protected]> 版权所有 (c) 2000
许多人提供了有益的建议和贡献,特别是
Ronald J Kimball <[email protected]>
Hugo van der Sanden <[email protected]>
Peter Scott <[email protected]>