2008-09
02

开发中遇到的几点问题:

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

更多内容请点开看。

阅读全文 »
2008-09
01

应该很多人都在为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不兼容,于是就无法正常调试了。

阅读全文 »
2008-08
25

这篇文章,对于能看懂的同学是非常非常重要的资料,对于看不懂的同学就一点用处都没有啦。

调查一下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是不起作用的。


2008-05
29

哦……Perl真的是太博大精深了。尤其是它的One-Liner程序,每一行都是优美的杰作啊。

下面搜集了一些很有用的One-Liner。大部分资料来自于 这里这里这里

阅读全文 »
2008-03
27

今天继续翻译代码布局的这一部分。有些很明显的条目,就不翻译了,看看代码就应该明白是什么意思。

阅读全文 »


2008-03
18

今天在写代码时遇到一个很奇怪的问题。我写的这样一个函数:

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的上下文不熟悉的人,这里可要特别注意啊。


2008-03
11

继续翻译Perl最佳实践的2.9~2.12。

阅读全文 »


2008-03
08

停止已久的《Perl Best Practices》翻译今天重新开始了。继续讲代码布局。

阅读全文 »


2008-03
07

最近遇到的一个任务是要将散落在系统各个文件中的 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都会被加载。


2008-01
29

上周偶然与fcicq讨论到一个关于perlcc的优化问题。 据说用perlcc将perl程序编译成C程序后再用gcc -O3进行优化,速度可能会快一些。 于是就测了测,顺便试了试其他语言的情况。

测试程序是Ackermann函数。 也许用它来做benchmark不太合适,但毕竟这是个纯数学+多次递归+耗时的运算,也能反映一定问题吧。

阅读全文 »