开发中遇到的几点问题:
1. split的分隔符不能使用字符串,只能使用正则表达式。
用perl -de 1; 启动后做实验:
DB<1> x split '\\', 'a\\b\\c' Trailing \ in regex m/\/ at (eval 13)[/usr/lib/perl5/5.8/perl5db.pl:628] line 2. DB<2> x split '\\\\', 'a\\b\\c' 0 'a' 1 'b' 2 'c' DB<3> x split /\\/, 'a\\b\\c' 0 'a' 1 'b' 2 'c'
试图指定分隔符为反斜杠,但 '\\' 出错,原因是perl会将第一个参数(字符串)的值作为正则表达式来解释, 结果 '\\' 就变成了 /\/ ,显然这是错误的正则表达式。 正确的写法是 '\\\\' 被解释成 /\\/,或者直接写 /\\/。
读了bkブログ的文章后发现, perl的split函数是忽略末尾的空元素的,想让它不忽略,必须加个参数-1:
DB<38> x split /,/, 'a,b,,c,,' 0 'a' 1 'b' 2 '' 3 'c' DB<39> x split /,/, 'a,b,,c,,',-1 0 'a' 1 'b' 2 '' 3 'c' 4 '' 5 ''
更多内容请点开看。
阅读全文 »应该很多人都在为mod_perl的调试方法发愁吧。通常只能使用print,或者输出到syslog中, 但都需要猜测错误位置并添加相应的日志输出,然后重启服务器,刷新,看结果…… 虽然使用Apache::Reload能减少重启服务器的麻烦,但Apache::Reload用多了就会出错,而必须重启, 何况打日志总不像交互式调试器方便。
mod_perl的官方文档中写明了如何使用调试器来调试, 不过这个方法在RHEL4下似乎不太好用(自己编译mod_perl的同学就应该没这个问题)。 查看了一下,原来RHEL4自带的mod_perl是 mod_perl-1.99_16-4, 而在官方的mod_perl的下载页面上最高版本只有 1 系的 1.30 和2系的2.0.4,并没有所谓的1.99版。 看这个mod_perl-1.99的内容,应该是1系和2系的混合体吧。 Apache::DB这个包与mod_perl-1.99不兼容,于是就无法正常调试了。
阅读全文 »这篇文章,对于能看懂的同学是非常非常重要的资料,对于看不懂的同学就一点用处都没有啦。
调查一下mod_perl下BEGIN/INIT/CHECK/END等块的行为如何?
测试程序,保存为life.cgi:
#!/usr/bin/perl
print "pid = $$\n";
print "Start main running here\n";
BEGIN { print "BEGIN\n"; }
INIT { print "INIT\n"; }
CHECK { print "CHECK\n"; }
END { print "END\n"; }
如果直接执行就是这样的:
$ ./life.cgi BEGIN CHECK INIT pid = 4610 Start main running here END
不论执行多少次,结果都相同(除了每次的pid不同之外)。但如果放到mod_perl下,结果就完全不一样了。 先单进程启动(httpd -X),再从浏览器中访问,结果如下:
BEGIN pid = 4557 Start main running here END
刷新一次,结果是:
pid = 4557 Start main running here END
可见,mod_perl下没有INIT和CHECK过程,只有BEGIN和END。其中END在每次请求时必然执行, 但BEGIN只有在进程刚创建后的第一次请求时才执行,以后的请求就不执行了。
如果你用了 Apache::Reload,那么可以修改一下life.cgi试试看,哪怕是一点小小的改动也行。 修改之后刷新,可以看到BEGIN又回来了,不过再刷一次就没有了。 所以Apache::Reload会在重新加载程序后再执行一遍BEGIN。
根据上述结论再引申一下,use = BEGIN { requre + import }, 可以推断,在同一进程下两次加载执行了use的页面,那么第二次的use是不起作用的。
哦……Perl真的是太博大精深了。尤其是它的One-Liner程序,每一行都是优美的杰作啊。
下面搜集了一些很有用的One-Liner。大部分资料来自于 这里、 这里、 这里。
阅读全文 »今天继续翻译代码布局的这一部分。有些很明显的条目,就不翻译了,看看代码就应该明白是什么意思。
今天在写代码时遇到一个很奇怪的问题。我写的这样一个函数:
sub parse {
my $self = shift;
my $header = shift;
my $charset = shift;
# 其它处理
# ...
}
至于如何调用,则是先通过 MIME::Parser 模块解析邮件, 然后调用解析结果中的 MIME::Head::get 函数来获得邮件头的值, 再将其结果传递给 parse 函数:
print $self->parse($head->get("Cc"), "GB2312");
通常都没有问题,在parse函数中,$header取到 Cc 邮件头,而 $charset 取到 "GB2312"; 但当一封邮件中不存在Cc时,就会出现一件怪事: $header 变量竟然取到了第二个参数 "GB2312" 的值!
在 parse 中将 @_ 打出来,发现当Cc邮件头取不到时,第一个参数就“消失”了, 参数列表就变成了 ("GB2312")。按照常理,参数列表应该是 (undef, "GB2312")才对啊。
百思不得其解中,偶然看到 MIME::Head::get 的文档中这样说:
- If a numeric INDEX is given, returns the occurence at that index, or undef if not present:
- If no INDEX is given, but invoked in a scalar context, then INDEX simply defaults to 0:
- If no INDEX is given, and invoked in an array context, then all occurences of the field are returned:
上面的调用属于没有 INDEX 的情况,莫非parse函数的参数列表提供了一个列表环境, 导致MIME::Head::get返回了一个数组?
于是强制为它提供一个标量环境,结果就正常地得到了 (undef, "GB2312")。
print $self->parse(scalar $head->get("Cc"), "GB2312");
看来果然是上下文环境造成的问题。parse函数的第一个参数期待一个常量, 而函数的参数列表却是一个列表环境,再加上MIME::Head::get在列表环境中返回数组, 这样就会导致两种错误:
- 如果不存在Cc,则参数列表为("GB2312"),$header = "GB2312";
- 如果存在多个Cc,则参数列表为("Cc-val1", "Cc-val2", "Cc-val3", "GB2312"), $charset="Cc-val2"。
两种情况都十分荒谬。
对Perl的上下文不熟悉的人,这里可要特别注意啊。
继续翻译Perl最佳实践的2.9~2.12。
停止已久的《Perl Best Practices》翻译今天重新开始了。继续讲代码布局。
最近遇到的一个任务是要将散落在系统各个文件中的 use lib '/opt/mysoft/lib'; 这样的绝对路径引用改成相对路径, 以便整个系统能够轻易地移动到其他的目录中。系统是工作在Apache2 + mod_perl 下的,cgi程序位于 /opt/mysoft/cgi-bin 下。
初看起来似乎很容易:
use lib '../lib'; # 错误!
但实际尝试一下就会发现,这里的 '..' 似乎找错了位置。当前目录并不是cgi程序所在的 /opt/mysoft/cgi-bin。 经过一番实验后,发现在 use lib 时,当前目录居然是在 / 下!这样这条路就走不通了。
另一个想法是通过 $0 获取到CGI程序的路径,再根据此路径来找到 lib 目录不就可以了么?
$dir = dirname $0; use lib "$dir/../lib"; # 错误! print "$_\n" for @INC;
实际运行就会发现,这一招也行不通。原因是use lib是在编译时执行的。编译时代码并没有被执行, 因此 $dir 变量为空(甚至都不存在),于是就成了 use lib "/../lib",当然不正确了。 这样看来 use lib 似乎必须要书写完整路径了。
CPAN文档的解释如下:
lib - manipulate @INC at compile time
实际上 use lib 可以理解为 BEGIN { unshift @INC, '/opt/mysoft/lib'; }。 BEGIN段的内容在这一段编译结束后立即执行。
回到本来的问题上,如何能避免在系统中使用绝对路径? 其实使用mod_perl的功能一次性地指定好@INC,那么在CGI和pm模块中就无需再use lib了。
在mod_perl的官方文档中有关于如何调整@INC的说明。
一种方法是在 startup.pl 中书写完整路径:
use lib '/opt/mysoft/lib';
令一种方法是直接在httpd.conf中修改@INC:
PerlSwitches -I/opt/mysoft/lib
这样虽然不能完全避免绝对路径,但至少在产品代码中可以不再使用 use lib 了。
同样地,use 也是在编译时执行的。(参考)use Module;相当于
BEGIN { require Module; Module->import( LIST ); }
程序代码中的所有use指令都会在代码执行前被执行,因此use不论放在哪里,效果都是一样的。因此
if ($use_first_lib) {
use FirstLib;
...
}
else {
use SecondLib;
...
}
这样的代码是毫无用处的,不论$use_first_lib的值如何,FirstLib和SecondLib都会被加载。
上周偶然与fcicq讨论到一个关于perlcc的优化问题。 据说用perlcc将perl程序编译成C程序后再用gcc -O3进行优化,速度可能会快一些。 于是就测了测,顺便试了试其他语言的情况。
测试程序是Ackermann函数。 也许用它来做benchmark不太合适,但毕竟这是个纯数学+多次递归+耗时的运算,也能反映一定问题吧。
阅读全文 »