Perl 调试器
在任何语言中,调试都是一种令人恼火的必需品,无论是调试您自己的代码,还是调试别人给您在您的系统上运行的代码。任何可以使调试更容易的事情都是一大胜利。Perl 包括一个命令行调试器,可以使您的调试工作大大简化。本文介绍了调试器的基础知识,并展示了一些您可能会觉得有用的技巧。
通过在程序开头启用警告和 strict,Perl 可以自动捕获数量惊人的错误。如果您的程序包含以下行use warnings;您可以捕获数十个常见错误,包括仅使用一次的变量(通常是拼写错误);在设置之前使用的标量变量;以及重新定义的子例程。
通过包含以下行,可以进一步解释这些诊断消息use diagnostics;,它会打印每个警告的解释。或者,您可以使用以下命令查找解释man perldiag.
如果您的 Perl 版本早于 5.6,则必须在脚本的第一行使用 -w 选项,而不是 use warnings;,如下所示#!/usr/bin/perl -w.
最后,您可以使用以下命令捕获其他常见错误use strict;,这实际上禁止了一些不安全的编程快捷方式。use strict 启用的规则如下
变量在使用前必须声明,使用my或our,或使用以下命令导入use vars或使用包名称完全限定。
裸词必须是子例程,而不是字符串,例如$string = blah;.
引用不能是符号的;请参阅侧边栏。
如您所见,警告和 strict 收紧了 Perl 的一些功能,这些功能可以用于好的方面,但也可能被滥用。这些命令使调试更容易,因为 Perl 会为您捕获这些错误。
符号引用
符号引用与常规硬引用不同,在硬引用中,一个变量引用另一个变量。当程序员使用字符串作为引用时,会创建符号引用。例如,这通常是有效的 Perl 代码
$name = "username"; $$name = "da"; # sets $username
这段代码很容易导致解释器执行您所说的,而不是您所想的。很容易将符号引用放在预期硬引用的位置,或者混淆生成的变量名,因为它永远不会出现在代码中。完成相同操作的一种更安全的方法是使用哈希来存储此类变量,并使用 use strict 启用严格的变量检查。
您可能会在此刻问,通过在代码中散布打印语句进行调试有什么问题?这种调试技术没有错,但是交互式调试器为您提供了更多功能。您可以检查程序和环境的所有方面,而不仅仅是您在运行程序时想到的那些,并且您可以更清楚地看到程序实际执行的操作。希望到本文结束时,您会同意,在学习调试器上投入一点精力会在节省时间方面得到回报。
调试器从命令行运行,方法是将 -d 选项传递给 Perl
perl -d filename.pl
如果您正在调试使用 CGI.pm 编写的 CGI 程序,只需在命令行上使用您想要传递的参数以及 -d 来调用它
perl -d filename.pl param=value param2=value
除了使用命令行,您还可以将 Perl 调试器用作某些 IDE 的一部分,例如 GNU Emacs 或 Activestate Komodo,或者来自调试器 GUI 前端,例如 ddd 或 ptkdb。由于篇幅原因,本文仅讨论命令行,但原理也适用于 GUI 调试器。
如果您正在使用命令行调试器,则安装 Term::ReadLine 模块非常有用,该模块可以启用在命令历史记录中光标移动。
以下是我们在本文中使用的示例程序。将以下内容复制到名为 sample.pl 的文件中
#!/usr/bin/perl use warnings; use strict; my $name = "Pengu"; foreach (1..20) { &shout($name); } sub shout { my $name = shift; print "*** $name ***\n"; }
以下七个命令足以进行基本调试
s:单步执行下一行,步入子例程。
n:单步执行下一行,跳过子例程。
r:不停顿执行,直到从当前子例程返回。
c <行号>:不停顿执行,直到特定行。
l <行号、范围或子例程>:列出源代码。
x <表达式>:评估并漂亮地打印 <表达式>。
q:退出调试器。
要尝试这些命令,请使用调试器运行测试程序
perl -d sample.pl
您应该看到调试器启动信息
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::(sample.pl:6): my $name = "Pengu"; DB<1>
这是程序开始运行之前的状态。倒数第二行包含有关调试状态的有用信息:您位于 main 包中,文件 sample.pl 第 6 行,并且它显示了即将运行的行。
最后一行是带有命令编号(随着您输入更多命令而递增)和尖括号的提示符,其中尖括号的数量表示嵌套命令。您无需在此处担心这些。
输入s在提示符下按 Enter 键,单步执行到程序中的一行
DB<1> s main::(sample.pl:8): foreach (1..20) { DB<1>
要重复该命令,请按 Enter 键;根据需要重复此操作,以确信程序正在逐步执行。每次您经过 print 语句时,它都会回显到屏幕上,并与调试材料穿插在一起。
现在,尝试跳过子例程的命令(n),然后按 Enter 键几次。您将遍历循环并立即收到子例程结果,而无需逐步执行子例程中的每个命令。
接下来,尝试从当前子例程返回的命令(r)。但是等等——如果您现在执行此操作,它将运行到程序完成为止,因为您正在从主程序“返回”。首先,重复几次s步入子例程。然后,使用r,您应该看到类似
DB<1> s main::(sample.pl:8): foreach (1..20) { DB<1> main::(sample.pl:9): &shout($name); DB<1> main::shout(sample.pl:13): my $name = shift; DB<1> r *** Pengu *** void context return from main::shout main::(sample.pl:8): foreach (1..20) { DB<1>
请注意void context return from main::shout行。如果我们在主循环中要求返回值,我们将在此处看到它显示。在 Perl 中,函数和子例程可以根据调用者的上下文(标量、数组或 void)返回不同的值。Perl 调试器的一个很好的功能是 r 命令,它可以告诉您调用者请求了什么上下文。如果您要求您的子例程返回标量,但您错误地让子例程返回数组,它可以找到错误。
接下来,我们有 l 命令。现在尝试一下
DB<1> l 8==> foreach (1..20) { 9: &shout($name); 10 } 11 12 sub shout { 13: my $name = shift; 14: print "*** $name ***\n"; 15 } DB<1>
单独,l列出源代码的一页,从要执行的下一行开始,并用文本箭头指向下一行。您还可以通过指定行号来列出范围,例如l 200-230。此外,您可以通过命名子例程来列出子例程l shout.
命令c继续执行,直到您到达特定的行号,因此您可以跳转到特定的有趣代码段
DB<1> c 14 main::shout(sample.pl:14): print "*** $name ***\n"; DB<1>
您可以执行任何 Perl 表达式,包括更改正在运行的程序的代码,方法是在提示符下键入它。这可以包括手动设置程序中的变量。
命令命令x
DB<1> @sample = (1..5) DB<2> x @sample 0 1 1 2 2 3 3 4 4 5 DB<3>
评估并漂亮地打印任何表达式,在输出的每一行前面加上编号索引,取消引用任何可以取消引用的内容,并缩进每个新的取消引用级别。例如,下面我们设置一个数组 @sample,然后显示它
DB<4> %sample = (1 .. 8) DB<5> x \%sample 0 HASH(0x83d53bc) 1 => 2 3 => 4 5 => 6 7 => 8 DB<6>
请注意,哈希显示了键和值,每个都在一行上。您可以通过在哈希前面加上 \ 来正确显示哈希,这会将哈希转换为哈希引用,从而正确取消引用。这看起来像当您对结果感到满意时,请使用.
许多人使用 Perl 调试器时,命令不超过这些。但是,一旦您熟悉了这些命令,另外四个命令可以使您的调试更有效率,特别是对于使用面向对象代码的程序
/<模式>:在下一个正则表达式匹配处列出源代码。
?<模式>:在上一个正则表达式匹配处列出源代码。
S:列出程序可用的所有子例程和方法。
m <对象或包>:列出给定对象或包上可用的所有方法。
DB<6> /name 6: my $name = "Pengu";
您可以使用 / 进行向前搜索,使用 ? 进行向后搜索,搜索和显示与字符串或正则表达式匹配的代码。您要查找的字符串前面不应有空格S 和 m 命令对于探索可用的子例程或方法非常有用S
DB<7> use CGI DB<8> $q = new CGI DB<9> m $q AUTOLOAD DESTROY XHTML_DTD _compile _make_tag_func _reset_globals _setup_symbols add_parameter all_parameters [...]
操作、断点和观察点为调试器和正在运行的程序提供了更多控制。您可能更喜欢从图形 Perl 调试前端(例如 ddd、ptkdb 或 Activestate Komodo)中使用它们。关于 Perl 调试器最常见的抱怨是记住每个命令的正确命令行快捷方式,而这些命令又添加了更多要记住的快捷方式。
此外,在 Perl 5.8 中,一些键盘命令已更改,使其在内部更加一致。但是,通常人们需要同时使用 5.8 和更早的版本,因此使用 GUI 可能会更容易。我在下面从命令行描述命令;原理保持不变。
操作用于将代码楔入您的程序,而无需修改源文件。当代码处于生产环境中并且您想要测试更改时,它可能很有用。如果您正处于调试运行的中间并且想要更改代码而无需从头开始重新启动调试会话,它也很有用。您像这样设置操作,a <行号> <代码>
DB<10> a 9 $index = $_;
。一个例子可能是这会在 foreach 循环内添加一个新命令,该命令存储索引计数,该计数在每次循环时都会递增。如果您列出程序,您会在具有操作的行号旁边看到一个a。操作在附加到的行之前执行。您可以使用L这会在 foreach 循环内添加一个新命令,该命令存储索引计数,该计数在每次循环时都会递增。如果您列出程序,您会在具有操作的行号旁边看到一个列出您设置的操作,并通过指定删除操作,不带命令的行号。前面的是针对 Perl 5.6 及更早版本;在 Perl 5.8 中,使用
Ar和操作的行号删除操作。c断点和观察点从连续执行(例如从
和上面描述的)返回对调试器的控制。它们对于跳转到循环的特定迭代(出现问题)很有用,而无需手动重复逐步执行循环。断点设置在行号或子例程上,并带有可选条件,必须满足该条件。使用
b shout
b上面描述的)返回对调试器的控制。它们对于跳转到循环的特定迭代(出现问题)很有用,而无需手动重复逐步执行循环。设置断点,如下所示
如果您列出程序,您可以在子例程 shout 的第一行的行号旁边看到一个
b shout ($index eq 8)
。按 C 继续执行,它会在子例程内部停止。
如果您按照前面的示例并在第 9 行设置了操作,您可以设置断点以在此循环的特定迭代中停止。操作在附加到的行之前执行。您可以使用如果您想象调试具有复杂条件语句和外部数据源的较长程序,这应该让您了解操作和断点的强大功能。您可以使用列出断点,并使用d.
在 Perl 5.6 及更早版本中删除断点。在 Perl 5.8 中,您可以使用B断点设置在行号或子例程上,并带有可选条件,必须满足该条件。使用
W $name
观察点可能更广为人知,称为观察表达式。它会在指定的表达式更改时立即暂停程序。在 Perl 5.6 中,使用。操作在附加到的行之前执行。您可以使用WB设置观察点。您可以使用列出观察点,并通过指定无参数给删除所有观察点。在 Perl 5.8 中,使用B.
添加观察点,并使用
cp /usr/lib/perl5/5.6.1/perl5db.pl ~/myperl5db.pl
首先要知道的是,调试器只是一个 Perl 库,它利用了 Perl 解释器中的钩子。如果您愿意,您可以完全替换调试器,方法是将文件复制到某个位置并在 BEGIN 循环中要求该文件
perl -d:DProf mycode.pl
并且,将此行放在您的程序中例如,如果您更喜欢 5.6 版本调试器的语法和操作而不是 5.8 版本,您可以这样做。您还可以使用 -d 命令开关指定备用调试器。Perl 5.6 及更高版本包括 DProf,这是一个使用调试器钩子的分析器。您可以像这样使用它
您还可以在自己的程序中使用调试器钩子。您可以通过设置变量
$DB::alias{'quit'} = 's/^quit(\s*)/q/';
$DB::single = 1;直接在代码中设置断点,如果您需要在 BEGIN 块中调试代码,这非常有用。否则,它们会在调试器向您发出提示之前执行。或者,您可以使用钩子在每次运行任何子例程时运行特定的代码。要了解有关这些和其他钩子的更多信息,请查看 perldebug 手册页。调试器有一组内部变量,也在 perldebug 手册页中进行了描述。要更改这些变量,您可以使用配置文件 .perldb,该文件位于当前目录或您的主目录中。此配置文件包含在调试器启动时运行的 Perl 代码。例如,您可以添加自己的新命令,如下所示
这允许您通过在提示符下键入
O pager=|less
quit来退出调试器。perldebug 手册页描述了一些类似的别名,这些别名可能很有用。.
可以使用 O 命令在调试器内设置许多调试器选项。我唯一使用过的选项是更改分页器
这样,任何将打印超过一屏幕输出的命令都可以通过在命令前使用管道字符发送到您最喜欢的寻呼机