2008-09
04

昨天一个同事问我关于Perl中的 -| 描述符的问题。 他的程序大概是这样的:

unless (open FH, "-|") {
  exec "foo bar";        # 用exec执行另一个程序
  exit;
}
while (<fh>) {
  ...
}
close FH;
$ret = $? >> 8;
if ($ret == 1) {
  ...
}

那么这里的 open FH, "-|" 是什么意思?$? >> 8 又是什么意思?

/fh

阅读全文 »
2008-09
03

在Perl中解析XML的方法最常见的就是使用 XML::DOM 和 XML::Simple了。 XML::DOM过于庞大,而且解析结果是一个DOM树,操作也不方便。 对于小型且不复杂的XML文件,XML::DOM真是杀鸡用牛刀。 这时就轮到轻便的XML::Simple上场了。

XML::Simple如其名,真的很简单。假设XML内容如下:

<opt>
    <user login="grep" fullname="Gary R Epstein" />
    <user login="stty" fullname="Simon T Tyson" >
        <session pid="12345"/>
    </user>
    <text>This is a test.</text>
</opt>

那么只需这样写:

use XML::Simple;
use Data::Dumper;

$xml = XMLin('sample.xml');
print Dumper($xml);

就可以轻而易举地将XML解析成一个hash,然后用foreach依次处理即可。

阅读全文 »
2008-09
02

开发中遇到的几点问题:

1. split的分隔符不能使用字符串,只能使用正则表达式。

用perl -de 1; 启动后做实验:

  DB&lt;1> x split '\\', 'a\\b\\c'
Trailing \ in regex m/\/ at (eval 13)[/usr/lib/perl5/5.8/perl5db.pl:628] line 2.
  DB&lt;2> x split '\\\\', 'a\\b\\c'
0  'a'
1  'b'
2  'c'
  DB&lt;3> x split /\\/, 'a\\b\\c'
0  'a'
1  'b'
2  'c'

试图指定分隔符为反斜杠,但 '\\' 出错,原因是perl会将第一个参数(字符串)的值作为正则表达式来解释, 结果 '\\' 就变成了 /\/ ,显然这是错误的正则表达式。 正确的写法是 '\\\\' 被解释成 /\\/,或者直接写 /\\/。

读了bkブログ的文章后发现, perl的split函数是忽略末尾的空元素的,想让它不忽略,必须加个参数-1:

  DB&lt;38> x split /,/, 'a,b,,c,,'
0  'a'
1  'b'
2  ''
3  'c'
  DB&lt;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-08
19

Perl对YAML的支持很好,著名的Plagger就是用YAML作为配置文件的。

在Perl中读取YAML文件也很简单。常用的模块就是YAML,这是个纯Perl的实现,速度慢,但可以跨平台运行。 如果追求运行速度而不在乎平台问题,可以使用YAML::Syck和YAML::XS,两者都是C语言的实现。 YAML::Syck的C语言部分基于libsyck,而YAML::XS的C语言部分基于libyaml。 两者相比,YAML::XS稍稍快一点,而且它精确地实现了YAML标准1.1版的内容。

阅读全文 »
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。

阅读全文 »