我有一个不起作用的Perl脚本,而且我不知道如何开始缩小问题的范围。我能做什么?
注意:我添加这个问题是因为我真的很想在Stackoverflow中添加很长的答案。我在其他答案中一直保持外部链接,因此值得在这里。如果您要添加一些内容,请不要羞于编辑我的答案。
我有一个不起作用的Perl脚本,而且我不知道如何开始缩小问题的范围。我能做什么?
注意:我添加这个问题是因为我真的很想在Stackoverflow中添加很长的答案。我在其他答案中一直保持外部链接,因此值得在这里。如果您要添加一些内容,请不要羞于编辑我的答案。
Answers:
该答案旨在作为解决Perl CGI脚本问题的通用框架,最初作为Perl CGI脚本故障排除出现在Perlmonks上。它不是您可能遇到的每个问题的完整指南,也不是有关漏洞压缩的教程。这只是我调试CGI脚本二十年(加倍!)的经验的结晶。该页面似乎有许多不同的位置,而且我似乎忘记了它的存在,因此将其添加到StackOverflow中。您可以通过bdfoy@cpan.org向我发送任何意见或建议。它也是社区Wiki,但不要太过分。:)
打开警告,使Perl可以警告您有关代码中有问题的部分。您可以使用命令行从命令行执行此操作,-w
因此无需更改任何代码或向每个文件添加杂注:
% perl -w program.pl
但是,您应该通过向warnings
所有文件添加编译指示来强迫自己始终清除可疑的代码:
use warnings;
如果您需要除简短警告消息之外的更多信息,请使用diagnostics
编译指示获取更多信息,或查看perldiag文档:
use diagnostics;
服务器期望CGI脚本的第一个输出是CGI标头。通常,这可能print "Content-type: text/plain\n\n";
与CGI.pm及其衍生版本一样简单print header()
。某些服务器对STDERR
在标准输出(on STDOUT
)之前显示的错误输出(on )敏感。
添加此行
use CGI::Carp 'fatalsToBrowser';
到您的脚本。这还会将编译错误发送到浏览器窗口。在移至生产环境之前,请确保将其删除,因为额外的信息可能会带来安全风险。
服务器保留错误日志(或者至少应该保留)。服务器和脚本的错误输出应显示在此处。找到错误日志并查看错误信息。日志文件没有标准位置。在服务器配置中查找其位置,或询问服务器管理员。您也可以使用CGI :: Carp之类的工具 来保存自己的日志文件。
如果看到诸如“拒绝权限”或“未实现方法”之类的错误,则可能意味着Web服务器用户无法读取和执行您的脚本。在Unix上,建议将模式更改为755 :
chmod 755 filename
。切勿将模式设置为777!
use strict
吗?请记住,Perl首次使用时会自动创建变量。这是一项功能,但是如果您错误输入变量名有时会导致错误。实用程序
use strict
将帮助您找到那些错误。在您习惯之前,这很烦人,但是一段时间之后您的编程将大为改善,并且您可以自由地犯其他错误。
您可以使用-c
开关检查编译错误。专注于报告的第一个错误。冲洗,重复。如果您遇到了非常奇怪的错误,请检查并确保脚本的行尾正确。如果以二进制模式FTP,从CVS签出或其他不处理行尾转换的内容,则Web服务器可能会将您的脚本视为一大行。以ASCII模式传输Perl脚本。
如果您的脚本抱怨不安全的依赖关系,那么您可能正在使用该-T
开关打开异味模式,这是一件好事,因为它可以使您将未经检查的数据传递给外壳程序。如果它抱怨,它正在努力帮助我们编写更安全的脚本。来自程序外部(即环境)的任何数据均被视为污染。环境变量(例如PATH
和)
LD_LIBRARY_PATH
特别麻烦。您必须按照我的建议将它们设置为安全值或完全取消设置它们。无论如何,您都应该使用绝对路径。如果污点检查抱怨其他问题,请确保您未污染数据。有关
详细信息,请参见perlsec手册页。
从命令行运行时,脚本是否输出您期望的结果?标题是先输出,然后是空行吗?请记住,
如果您在终端上(例如,交互式会话),STDERR
可能会与之合并STDOUT
,并且由于缓冲,可能会以混乱的顺序显示。通过设置$|
为true值来打开Perl的自动刷新功能。通常,您可能会$|++;
在CGI程序中看到。设置后,每次打印和写入将立即进入输出而不是被缓冲。您必须为每个文件句柄设置此设置。使用select
更改默认的文件句柄,就像这样:
$|++; #sets $| for STDOUT
$old_handle = select( STDERR ); #change to STDERR
$|++; #sets $| for STDERR
select( $old_handle ); #change back to STDOUT
无论哪种方式,第一件事的输出都应该是CGI标头,后跟空白行。
Web服务器环境通常比命令行环境要受限制得多,并且具有有关请求的更多信息。如果您的脚本可以从命令行正常运行,则可以尝试模拟Web服务器环境。如果出现问题,则说明您有环境问题。
取消设置或删除这些变量
PATH
LD_LIBRARY_PATH
ORACLE_*
变量设置这些变量
REQUEST_METHOD
(设置为GET
,HEAD
或POST
适当)SERVER_PORT
(通常设置为80)REMOTE_USER
(如果您正在做受保护的访问操作)CGI.pm
(> 2.75)的最新版本要求该-debug
标志具有旧的(有用的)行为,因此您可能必须将其添加到CGI.pm
导入中。
use CGI qw(-debug)
die()
还是warn
?STDERR
除非您重新定义了这些功能,否则这些功能会打印到。它们也不输出CGI标头。您可以使用CGI :: Carp之类的软件包获得相同的功能。
如果您认为脚本执行正确的操作,并且在手动执行请求时获得正确的输出,则浏览器可能是罪魁祸首。在测试时,清除缓存并将缓存大小设置为零。请记住,有些浏览器确实很愚蠢,即使您告诉这样做,也不会实际重新加载新内容。这在URL路径相同但内容更改(例如动态图像)的情况下尤其普遍。
脚本的文件系统路径不一定与脚本的URL路径直接相关。确保您具有正确的目录,即使您必须编写简短的测试脚本来对此进行测试。此外,您确定要修改正确的文件吗?如果您所做的更改没有效果,则可能是您正在修改其他文件,或者将文件上传到错误的位置。(顺便说一下,这是造成此类麻烦的最常见原因;)
CGI.pm
还是它的衍生产品?如果你的问题与解析CGI输入和你没有使用广泛的测试模块一样CGI.pm
,CGI::Request
,
CGI::Simple
或者CGI::Lite
,使用模块和享受生活。
CGI.pm
具有cgi-lib.pl
兼容模式,可以帮助您解决由于较旧的CGI解析器实现而导致的输入问题。
如果您正在使用system
,后退标记或其他IPC工具运行外部命令
,则应使用指向外部程序的绝对路径。您不仅可以确切知道正在运行的内容,而且还可以避免一些安全问题。如果要打开文件进行读取或写入,请使用绝对路径。CGI脚本对当前目录的想法可能与您不同。另外,您可以明确chdir()
地将您放在正确的位置。
大多数Perl函数都会告诉您它们是否起作用,并且会$!
在失败时进行设置。您是否检查了返回值并检查$!
了错误消息?您检查了
$@
是否在使用eval
吗?
Perl的最新稳定版本是5.28(或不是,取决于它的最后编辑时间)。您使用的是旧版本吗?不同版本的Perl可能有不同的警告概念。
不同的服务器在相同情况下的行为可能不同。同一服务器产品在不同配置下的行为可能有所不同。在任何请求帮助中都应尽可能包含这些信息。
认真的CGI程序员应该尽可能了解服务器-不仅包括服务器功能和行为,还包括本地配置。如果您使用的是商业产品,则服务器的文档可能不可用。否则,文档应在您的服务器上。如果不是,请在网络上查找。
comp.infosystems.www.authoring.cgi
吗?这项功能很有用,但所有好的海报都已死亡或徘徊。
可能有人曾经遇到过您的问题,并且有人(可能是我)已经在此新闻组中回答了问题。尽管此新闻组已经过了鼎盛时期,但从过去收集的智慧有时还是有用的。
在大型系统中,由于发生了很多事情,可能难以跟踪错误。尝试使用尽可能短的脚本重现问题行为。知道问题是大多数解决办法。这可能确实很耗时,但是您尚未发现问题,并且选件已用完。:)
说真的 有时,我们对问题的关注如此之深,以至于我们发展出“感知范围缩小”(隧道视野)。休息片刻,喝杯咖啡,或在[杜克·努克(Duke Nukem),雷神之锤(Quake),毁灭战士(Doom),光晕(Halo),化学需氧量(COD)]中炸死一些坏人,可能会为您提供重新解决问题所需的崭新视角。
再次认真。有时大声解释问题会使我们得到自己的答案。与您的企鹅(毛绒玩具)交谈,因为您的同事没有在听。如果您对此感兴趣,可以将其作为一种认真的调试工具使用(如果您现在还没有发现问题,我建议您这样做),那么您可能还想阅读《计算机编程心理学》。
$|=1
代替$|++
呢?
$|=1
代替$|++
?它并没有真正的改变,即使那样,$|
也是神奇的。
use strict
通常在任何时候都fatalsToBrowser
可以使用,但在生产中可能不建议使用,尤其是在使用时die
。
die
语句和其他致命的运行时和编译时错误被打印到STDERR
,这可能很难找到,并且可能与站点上其他网页的消息混淆。在调试脚本时,最好将致命错误消息以某种方式显示在浏览器中。
一种方法是致电
use CGI::Carp qw(fatalsToBrowser);
在脚本的顶部。该调用将安装一个$SIG{__DIE__}
处理程序(请参阅perlvar),在您的浏览器中显示致命错误,并在必要时添加一个有效的标头。我之前从未听说过的另一个CGI调试技巧是与脚本上的and 工具CGI::Carp
一起使用eval
,以捕获编译时错误:DATA
__END__
#!/usr/bin/perl
eval join'', <DATA>;
if ($@) { print "Content-type: text/plain:\n\nError in the script:\n$@\n; }
__DATA__
# ... actual CGI script starts here
这种更为冗长的技术比之稍有优势CGI::Carp
,因为它将捕获更多的编译时错误。
更新:我从未使用过它,但是看起来像CGI::Debug
Mikael S所建议的那样,也是用于此目的的非常有用且可配置的工具。
<DATA>
是一个魔术文件句柄,可读取以开头的当前脚本__END__
。Join为它提供了列表上下文,因此<fh>返回一个数组,每一项一行。然后join将其放回一起(与”联接)。最后,评估。
eval join(q{}, <DATA>);
我不知道为什么没有人提到PERLDB_OPTS
称为的选项RemotePort
。尽管公认的是,网络上没有很多可用的示例(RemotePort
甚至在perldebug中也没有提到)-我想出这个示例有点麻烦,但是可以解决了(这是一个Linux示例)。
为了做一个合适的例子,首先我需要做一些可以对CGI Web服务器进行非常简单的模拟的东西,最好是通过单个命令行。找到用于运行cgis的简单命令行Web服务器之后。(perlmonks.org),我发现IO :: All- 一个小型Web服务器适用于此测试。
在这里,我将在/tmp
目录中工作;CGI脚本将/tmp/test.pl
(包含在下面)中。请注意,该IO::All
服务器仅在与CGI相同的目录中提供可执行文件,因此chmod +x test.pl
此处是必需的。因此,为了进行常规的CGI测试运行,我/tmp
在终端中将目录更改为,然后在其中运行单层Web服务器:
$ cd /tmp
$ perl -MIO::All -e 'io(":8080")->fork->accept->(sub { $_[0] < io(-x $1 ? "./$1 |" : $1) if /^GET \/(.*) / })'
webserver命令将在终端中阻止,否则将在本地启动Web服务器(在127.0.0.1或上localhost
)-之后,我可以转到Web浏览器,并请求以下地址:
http://127.0.0.1:8080/test.pl
......我应该遵守print
由制造的S test.pl
加载-和显示-在Web浏览器。
现在,要使用调试此脚本RemotePort
,首先,我们需要网络上的侦听器,通过该侦听器与Perl调试器进行交互;我们可以使用命令行工具netcat
(nc
,在这里看到:Perl如何远程调试?)。因此,首先netcat
在一个终端上运行侦听器-它将阻塞并等待端口7234(这将是我们的调试端口)上的连接:
$ nc -l 7234
然后,我们希望perl
在调用RemotePort
时以调试模式启动test.pl
(甚至在CGI模式下,通过服务器)。在Linux中,可以使用以下“ shebang wrapper”脚本来完成此操作-该脚本也必须位于中/tmp
,并且必须使其具有可执行性:
cd /tmp
cat > perldbgcall.sh <<'EOF'
#!/bin/bash
PERLDB_OPTS="RemotePort=localhost:7234" perl -d -e "do '$@'"
EOF
chmod +x perldbgcall.sh
这有点棘手-请参阅shell脚本-如何在shebang中使用环境变量?-Unix和Linux堆栈交换。但是,这里的技巧似乎不是派生perl
处理的解释器test.pl
-因此,一旦我们击中它,我们就不会这样做exec
,而是调用perl
“简单地”,并基本上使用“源”我们的test.pl
脚本do
(请参阅如何运行脚本)。来自Perl脚本中的Perl脚本?)。
现在,我们已经perldbgcall.sh
在/tmp
-我们可以改变的test.pl
文件,以便它指的是在它的shebang行(而不是通常的Perl解释器)这个可执行文件-这里是/tmp/test.pl
这样修改:
#!./perldbgcall.sh
# this is test.pl
use 5.10.1;
use warnings;
use strict;
my $b = '1';
my $a = sub { "hello $b there" };
$b = '2';
print "YEAH " . $a->() . " CMON\n";
$b = '3';
print "CMON " . &$a . " YEAH\n";
$DB::single=1; # BREAKPOINT
$b = '4';
print "STEP " . &$a . " NOW\n";
$b = '5';
print "STEP " . &$a . " AGAIN\n";
现在,它们test.pl
及其新的shebang处理程序perldbgcall.sh
都在/tmp
;并且我们正在nc
侦听端口7234上的调试连接-因此,我们最终可以在其中打开另一个终端窗口,将目录更改为/tmp
,然后运行单行网络服务器(它将侦听端口8080上的网络连接):
cd /tmp
perl -MIO::All -e 'io(":8080")->fork->accept->(sub { $_[0] < io(-x $1 ? "./$1 |" : $1) if /^GET \/(.*) / })'
完成此操作后,我们可以转到Web浏览器,并请求相同的地址http://127.0.0.1:8080/test.pl
。但是,现在,当网络服务器尝试执行脚本时,它将通过perldbgcall.sh
shebang 来执行-这将perl
在远程调试器模式下启动。因此,脚本执行将暂停-这样,Web浏览器将锁定,等待数据。现在我们可以切换到netcat
终端了,我们应该看到熟悉的Perl调试器文本-但是,通过nc
以下命令输出:
$ nc -l 7234
Loading DB routines from perl5db.pl version 1.32
Editor support available.
Enter h or `h h' for help, or `man perldebug' for more help.
main::(-e:1): do './test.pl'
DB<1> r
main::(./test.pl:29): $b = '4';
DB<1>
如代码片段所示,我们现在基本上将其nc
用作“终端”-因此我们可以r
为“ run” 键入(和回车)-脚本将运行断点语句(另请参见在perl中,$之间的区别是什么DB :: single = 1 and 2?),然后再次停止(请注意,此时浏览器仍将锁定)。
因此,现在我们可以说test.pl
,通过nc
终端逐步完成其余的:
....
main::(./test.pl:29): $b = '4';
DB<1> n
main::(./test.pl:30): print "STEP " . &$a . " NOW\n";
DB<1> n
main::(./test.pl:31): $b = '5';
DB<1> n
main::(./test.pl:32): print "STEP " . &$a . " AGAIN\n";
DB<1> n
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.
DB<1>
...但是,在这一点上,浏览器也会锁定并等待数据。只有在我们使用以下命令退出调试器之后q
:
DB<1> q
$
...浏览器是否停止锁定-并最终显示的(完整)输出test.pl
:
YEAH hello 2 there CMON
CMON hello 3 there YEAH
STEP hello 4 there NOW
STEP hello 5 there AGAIN
当然,即使不运行Web服务器也可以进行这种调试-但是,此处的整洁之处在于,我们根本不接触Web服务器。我们从网络浏览器“本地”触发执行(对于CGI)-CGI脚本本身唯一需要的更改就是shebang的更改(当然,shebang包装脚本的存在,作为可执行文件在同一文件夹中)目录)。
好吧,希望这对某人有帮助-我当然会喜欢偶然发现这一点,而不是自己写下来:)
加油!
老实说,您可以在这篇文章上做所有有趣的事情。我发现,最简单,最主动的解决方案是“打印”。
例如:(普通代码)
`$somecommand`;
查看它是否正在执行我真正想要的操作:(故障排除)
print "$somecommand";
值得一提的是,当您从命令行执行Perl脚本时,Perl总是会告诉您错误发生在哪一行。(例如SSH会话)
如果其他所有方法都失败,我通常会这样做。我将通过SSH进入服务器并手动执行Perl脚本。例如:
% perl myscript.cgi
如果有问题,Perl会告诉您。此调试方法消除了任何与文件权限有关的问题或Web浏览器或Web服务器问题。
您可以使用以下命令在终端中运行perl cgi-script
$ perl filename.cgi
它解释代码并提供带有HTML代码的结果。如果有错误,它将报告错误。
perl -c filename
确实只会检查语法。但是perl filename
会打印HTML输出。虽然不能保证不会出现500 CGI错误,但这是一个很好的第一个测试。