Accessing Facebook by LWP

Usually, we can get the content of a url by the following code in Perl

perl -MLWP::Simple -e 'getprint "http://www.google.com"';

But if you try to use it to access Facebook, like this

perl -MLWP::Simple -e 'getprint "http://www.facebook.com"';

you will get a page "Upadte your browser" instead of the real one. That's because Facebook doesn't like the default UserAgent of LWP or WWW::Curl.

The simplest way to solve it is to change the default LWP user agent string to your own string.

perl -e 'use LWP::Simple qw($ua get); $ua->agent("My agent/1.0"); print get "http://www.facebook.com";'

Log4perl多个Appender重复输出日志的问题解决办法

Perl开发中会经常用到Log4perl来输出日志,例如有类似如下配置:

log4perl.rootLogger = DEBUG, Screen
log4perl.logger.Utils = DEBUG, Screen

log4perl.appender.Logfile = Log::Log4perl::Appender::File
log4perl.appender.Logfile.filename = test.log
log4perl.appender.Logfile.layout = Log::Log4perl::Layout::PatternLayout
log4perl.appender.Logfile.layout.ConversionPattern = %r %F %L %m%n

log4perl.appender.Screen = Log::Log4perl::Appender::Screen
log4perl.appender.Screen.stderr = 1 
log4perl.appender.Screen.layout = Log::Log4perl::Layout::PatternLayout
log4perl.appender.Screen.layout.ConversionPattern = %M | %m%n

结果发现Utils中的消息,一次debug调用会输出两条同样的消息。Google一查发现还很常见,老祖宗Log4j下面也有类似的问题。作者在FAQ中提到了这个问题,解决办法除了与Log4j类似的解决方案之外,Log4perl还有一个简单的办法,只要在配置加入一行

log4perl.oneMessagePerAppender = 1 

Perl中不寻常的 ?: 运算符

前几天写一个perl的脚本 在:?运算符上遇到了一个很诡异的问题

$data->{$id}->{'total'} ?
    $data->{$id}->{'ratio'} = sprintf("%.2f%%", 100 * $data->{$id}->{'succ'} / $data->{$id}->{'total'}) : 
    $data->{$id}->{'ratio'} = 'N/A';

我的本意是 如果 $data->{$id}->{'total'} 未定义则不计算ratio,把ratio赋值为N/A. 这条语句等同于

if ( $data->{$id}->{'total'} ) {
    $data->{$id}->{'ratio'} = sprintf("%.2f%%", 100 * $data->{$id}->{'succ'} / $data->{$id}->{'total'});
} else {
    $data->{$id}->{'ratio'} = 'N/A';

可奇怪的是,当无论total是否有定义 ratio的结果居然都是N/A. 可后面if else的语句是没有问题的,真的是让我百思不得其解。跑去查Perl的文档,其中对于?:的运算符号的解释是

Ternary ``?:'' is the conditional operator, just as in C. It works much like an if-then-else. If the argument before the ? is true, the argument before the : is returned, otherwise the argument after the : is returned.

貌似是return the argument,于是乎脑子里突然闪过一个念头, 在前后都加上了括号...

$data->{$id}->{'total'} ?
    ( $data->{$id}->{'ratio'} = sprintf("%.2f%%", 100 * $data->{$id}->{'succ'} / $data->{$id}->{'total'}) ) : 
    ( $data->{$id}->{'ratio'} = 'N/A' );

...居然就对了。既然是return the argument,我就又换了一种方式:

$data->{$id}->{'ratio'} = $data->{$id}->{'total'} ?
    sprintf("%.2f%%", 100 * $data->{$id}->{'succ'} / $data->{$id}->{'total'}) : 
    'N/A';

虽然后面两种方式都可以理解,那确实是一种正确的做法。 但为什么第一种方式的结果不对呢?我又写了一个简单的小程序测试

#!/usr/bin/perl

use strict;

my $total=1;
my $rval;

############################`
$total ? 
    $rval = $total :
    $rval = 'N/A';

print $rval, "\n";

############################
$total ? 
    ( $rval = $total ) :
    ( $rval = 'N/A' );

print $rval, "\n";

############################
$rval = $total ? $total : 'N/A';

print $rval, "\n";

############################
if ($total) {
    $rval = $total;
} else {
    $rval = 'N/A';
}

print $rval;

运行的结果显示, 无论第5行给$total赋什么值...包括1, "abc", "true", undef 等,执行的结果第一个print打印出来的都是N/A。 难道 $total? 不等价于 if ($total) 吗?

后来偶然的一次机会在PerlChina上问过此问题,才发现原来C和Perl对于:?=的优先级定义是不同的。在C中,=的优先级高于:?,而Perl中则正好相反。这直接导致了第一种情况对于语句的解释顺序与C截然不同。对于x ? a = 1 : a = 2;,你期望解释为x ? (a = 1) : (a = 2);,实际却解释成了另外一个形式。

因此教训就是,文档在解释 ?: 的时候说的很清楚 If the argument before the ? is true, the argument before the : is returned,重点在return。所以,:? 里面应该尽量写“表达式”,而非“语句”。非要用语句,那么请加括号避免优先级问题。

Perl与数据库DBI快速入门

上次的文章Perl无废话入门指南中,简单的介绍了Perl的开发环境。为了完成提到的任务,还需要做几个知识点。无论是写脚本还是做CGI,如何使用Perl来访问数据库就是很有用的一个知识。

各种语言和开发环境访问数据库有各种不同的方式,比如可以用C和数据库提供的API接口来进行访问,也可以用JDBC、ODBC、ADO等封装好的统一接口来进行访问。Perl访问数据库最常用的包是DBI,可以在www.cpan.org找到。另外还需要安装对应数据库的驱动包,例如DBD::MySQL、DBD::Oracle、DBD::Sybase或者DBD::ODBC等。具体的安装方法请参考上期文章,在此就不赘述了。

下文以常见的MySQL为例,说说如何实现对数据库的操作。

1 基本流程

习惯在Windows下开发数据库、熟悉ADO、ADO.NET的朋友,一定对ADOConnection/ADODataSet/ADOTable等类耳熟能详。DBI的接口与之类似,但在操作方法上又有不同,对ADO熟悉的朋友不妨比较一下异同。一般来说,数据库操作由以下几个步骤组成一个常见的流程:

1. 建立一个数据库连接

2. 通过建立的数据库连接,执行SQL语句

3. 执行SQL后获取返回的数据集

4. 在数据集中对记录进行处理,一般是一个循环的过程

5. 处理完毕,关闭数据库连接,释放资源

下面是按照上述的流程,在Perl中访问MySQL的一段代码,以这段代码为例,详细说明DBI的使用方法。

#!/usr/bin/perl -w
use strict;
use DBI;

my $dbh = DBI->connect("DBI:mysql:test:192.168.1.2", 'root', 'password');
my $sth = $dbh->prepare("SELECT * FROM test1");
$sth->execute();

while ( my @row = $sth->fetchrow_array() )
{
       print join('\t', @row)."\n";
}

$sth->finish();
$dbh->disconnect();

注意代码中的灰色部分就是要特别关注的数据库访问接口,这里展现的只是一部分,下面将会依次说明每一个步骤,以及其它的操作在Perl中是如何实现的。

1.1 连接数据库

my $dbh = DBI->connect("DBI:mysql:test:192.168.1.2", 'root', 'password');

调用DBI的方法DBI->connect来建立一个数据库的连接,如果连接成功则返回一个数据库连接句柄,之后执行SQL等操作都要把这个连接句柄作为一个操作参数。在connect调用中,首先要提供一个数据库连接串。这个连接串用冒号分为了几个部分,请看下表

小节 说明
DBI 接口类型
mysql 数据库类型
test 数据库名称
192.168.1.2 数据库主机地址

在前面例子中的连接串中,DBI表示这是DBI接口的一个连接串;mysql表示要连接的数据库是MySQL数据库(如果要连接Oracle数据库,这里则是oracle),不同的数据库有不同的连接串定义,可以参考DBI对应的访问驱动的说明;test指明了连接到数据库主机上的数据库名称;192.168.1.2就是MySQL服务器的IP地址。这里要注意的是,连接串中的数据库类型mysql必须小写。如果省略了主机名,则缺省为localhost。connect方法的后面两个参数是连接数据库主机的用户名和密码,这个可是不可缺少的 J

如果在连接过程中出现任何错误,则connect的返回值都会是undef(和C语言中的NULL是一回事)。这里为了简化而略去了错误检查,实际做项目的时候应当对这些错误和返回值的进行检查。

1.2 执行SQL语句

my $sth = $dbh->prepare("SELECT * FROM test1");
$sth->execute();

$dbh->do(“UPDATE test1 SET time=now()”);

连接上了数据库,获得了数据库连接句柄,就可以利用这个句柄来对数据库进行操作了。要执行一条SQL语句,为了提高性能,DBI分两个步骤来做。先把SQL语句通过prepare方法提交到数据库,数据库为该语句分配执行资源,之后调用execute方法通知数据库执行该SQL语句。注意prepare方法是通过数据库连接句柄调用的,如果成功则返回一个该SQL的句柄,之后通过该SQL语句句柄调用execute执行SQL。 一般来说execute执行的都是返回数据的语句(例如SELECT语句)。反之如果执行INSERT、UPDATE、DELETE、CREATE TABLE等不需要返回数据的语句,则有一个更方便、快速的方法 $dbh->do(SQL语句),可以省去prepare的步骤。do方法返回的是受该SQL影响的记录数量。

1.2.1 技巧:对SQL进行排版

常写大段SQL的朋友可能会对于SQL中的引号很头痛,每每都因为引号的问题搞的SQL语句乱成一团分辨不清。还记得上篇文章讲过的qq吗?这里正是它的好用处。由于qq中的字符串同双引号” ”内的字符串一样会对变量进行解释,同时qq还可以换行。因此使用它来对SQL进行排版是非常好的一个选择,例如像这样的一条SQL语句:

my $res_operator = $dbhandle->prepare( qq{
       SELECT o_customerid, COUNT(*) AS totalMsgNum FROM mm4fcdrs
       WHERE (m_date>'$begindate') AND (m_date<'enddate') 
       GROUP BY o_customerid 
});

根本无需考虑引号的问题,可以和正常情况一样的写SQL,是不是方便了很多?

1.2.2 通过SQL语句中的参数优化查询执行效率

在执行大量INSERT之类的语句的时候,反复向数据库服务器提交同样结构的一个SQL语句,在这种情况下可以利用prepare和SQL参数来优化执行效率:

1.先使用prepare提交一个SQL模板给数据库服务器,把其中值的部分用参数占位符代替。 2.使用prepare让服务器为该SQL准备了执行资源后,调用execute并在该方法中传入参数实际的值执行SQL。 3.之后可以反复调用execute,不需要服务器重新prepare

假设要执行这样的一个系列的SQL

INSERT INTO test1 VALUES (NULL, ‘a’, ‘2005-04-01’)
... ...
INSERT INTO test1 VALUES (NULL, ‘z’, ‘2005-04-01’)

其中第二个字段的值是从a到z的字母。那么可以这样来优化执行效率:

my $sth = $dbh->prepare( qq{
    INSERT INTO test1 VALUES (NULL, ?, ‘2005-04-01’)
} );

for my $value('a'..'z')  {
    $sth->execute($value);
}

其中的问号就是前面说的参数占位符了,它的意思就是告诉在准备执行资源的服务器:这个SQL的这个位置会有一个值,但是现在还不知道,等下执行的时候再告诉你。 prepare了之后,用一个循环产生a-z的字符给变量$value,然后将$value在execute方法中作为一个参数传入,服务器那里会自动用传入的值替换前面的"?"。需要提醒的是,传入的参数个数一定要和SQL中的占位符的数量一样。

1.3 读取记录

熟悉ADO的朋友一定知道里面有一个DataReader对象,DBI中读取数据的方法和它非常的相似。简单来说,就是单向、流式的读取数据,也就是每次只能向后读一条数据直到没有数据可以读取。

文章开头的例子中,用了 $sth->fetchrow_array() 方法来读取数据。其实DBI读取数据还有几种常见的方法,这几个方法是类似的,所不同的是返回记录的形式。

1.3.1 fetchrow_array

返回一个由字段的值组成的数组。该数组的第1个元素就是当前记录第1个字段的值。

while ( my @row = $sth->fetchrow_array() )  {
    print "$row[0], $row[1], $row[2]\n";
}

或者这样,不过要注意字段对应的顺序

while ( my ($id, $name, $time) = $sth->fetchrow_array() )  {
    print "$id, $name, $time\n";
}

1.3.2 fetchrow_arrayref

返回由字段的值组成的数组的引用。同fetchrow_array的区别很明显,fetchrow_arrayref返回的数组的引用。

while ( my $row_ref = $sth->fetchrow_arrayref() ) {
    for (my $i = 0; $i < @{$row_ref}; $i++)       {
        print "$row_ref->[$i]\t";
    }
    print "\n";
}

这里要注意的是,如果要取字段的个数,需要把这个引用转成数组的形式获得 @{$row_ref} 。获取数组元素的值的时候,因为$row_ref是引用,因此需要使用->操作符。

1.3.3 fetchrow_hashref

返回一个由”字段名-字段值”这样的”键-值”对组成的HASH表。关键的不同就是,只有这个方法可以通过一个字段名获得它的值,而不必关心这个字段是第几个字段。而前者只能依靠索引来访问值。不过缺点就是,效率要比前面两个差一些。

while ( my $record = $sth->fetchrow_hashref() ) {
    for my $field( keys %{$record} ) {
        print "$field: $record->{$field}\t";
    }
    print "\n";
}

这里需要复习一下HASH表的操作方法。keys操作符获取HASH的键(key)的数组,$record->{$field}获得HASH表中$field对应的值。注意这里同样是引用,因此要用->操作符。

使用上面三个方法可以基本解决问题了。此外,还有两个方法fetchall_arrayrefselectall_arrayref可以直接通过SQL一次性获取整个数据集,不过使用上稍微复杂一些,要涉及到 perl的scalar 操作符,这里就不赘述了。有兴趣的读者可以参考DBI的相关资料。

最后是收尾工作。

1.4 结束一个SQL会话

$sth->finish();

1.5 断开数据库连接

$dbh->disconnect();

很简单明了,就不赘述了。

Perl中利用DBI访问数据库的接口基本上就是这些了,还有一些高级的内容留给有兴趣的读者自己发掘研究了。可能有些读者会感觉没有ADO、ADO.NET操作起来方便,但是在脚本的环境下能够如此方便的操作数据库,比起用C接口来说已经方便很多了。也许在看完这片文章之后的不久,可以在cpan上发现你的Module和全世界的Perl程序员一起分享呢。

2 参考资源

Perl无废话入门指南

最近接到一个任务是这样的,一台Solaris服务器上需要运行一个脚本,每天统计MySQL数据库中的数据并生成报表。本来这是一个可以就事论事的小项目,但是为了以后的灵活和可扩展性,我设计了一个使用XML做统计模版配置的方案。由于在Bash下不太好实现XML的访问,因此我考虑用Perl来实现这个脚本。

Perl是一个强大的脚本语言,本来是设计应用在文本处理方面的,但是后来发展的越来越强大,已经可以处理网络、图形、系统、文件等等各个方面的内容。Perl本身内置了丰富的操作符和函数,外部也有多年积累下来的大量模块。但是不知道什么原因在国内好像很少有人用。关于Perl的历史我就不多说了,有兴趣可以上网查一下。有一点要说的是,目前Perl最新的版本是5.8.6,而Perl6虽然已经设计很久了但是由于自举问题目前还没有一个可用的版本。为了解决这个问题,台湾的唐宗汉发起的Pugs项目正在快速的实施中,可能很快就能有结果了,有兴趣的朋友可以多多关注一下,也许还可以为开源世界做点贡献。

虽然很早以前就了解过Perl,但是从来就没有实际的用它做过项目,因此这次的实现是一个边学边做的过程。作为一个程序员,学习一种新的语言总会有一点惯性思维,加之Perl在语法上与C语言比较类似。因此我想在这片文章中主要以C为背景做一个比较。这种比较不是比较语言上的优劣,而是说明同样的功能如何在Perl中实现以及之间的区别。限于篇幅,具体的技术实现的细节我就不在这里多说了,你可以在末尾的资源一节中找到很多相关的文章。如果你没有接触过Perl,我想你可能更希望看到学习Perl的过程中可能会遇到的一些问题以及解决方法。

工欲善其事,必先利其器

要写代码,首先至少得有一个编辑器。Perl是跨平台的一种解释型语言,可以在Unix/Linux/Windows/Mac等平台上运行。具体对应平台上的编辑器,最简单的方案是Unix下用vi,Windows下用UltraEdit。当然也有商业化的IDE,不过我尝试了一下发现并不是那么的好用,因此我在Windows平台上以UltraEdit作为编辑环境,完成后迁移到Solaris平台上。

关于环境的搭建,有这样几个需要注意的地方:

1、 Windows平台下对应的是ActivePerl,可以免费下载。

2、 去UE的网站上下载Perl的AutoComp文件,可以实现自动完成功能。

3、 下载Perl对应的语法加亮的Tag文件并加入到UE中,可以更块的发现拼写错误。

4、 在UE设置一个快捷工具,命令行为C:\Perl\bin\perl.exe "%F"(捕获输出),可以实现快速运行并显示结果。

5、 如果你不喜欢UE,那么我推荐Vim

巧妇难为无米之炊

起始从某种角度来说,程序员和厨子是一样的。要做出一桌大餐来,首先得看看手上有什么原料,然后才能琢磨一下用这些东西能做出什么好吃的来。或者说想做什么东西,得先备好料才行。

看看我们现在都有什么:一个编辑器,一个Perl的开发环境,还有一个聪明的脑袋和满肚子的智慧。这个任务中,要处理命令行参数、访问MySQL数据库(SQL)、读写XML的配置文件以及输出一个固定格式的报表文件。

好了,去查查资料,看看访问数据库和读写XML都需要什么东西。正如同C语言本身带了很多标准函数库一样,Perl本身也有函数库,并把这些函数库称为Module(模块)。查了一下资料,发现要访问MySQL数据库需要DBI和DBD::MySQL两个模块,那么去哪里找这些模块呢。这里给大家介绍一个Perl的Module集散地 www.cpan.org,这里包含了八千多个Module,可以从这里下载到几乎各种各样的Module。可以手工下载后安装,也可以使用工具来自动安装。在Windows下是可以使用ppm进行自动安装,例如DBD的安装过程如下:

C:\>ppm
…
ppm> search DBI
Searching in Active Repositories
… 一大堆与DBI相关的包的列表,其中就包括DBI这个包
ppm> install DBI
…
ppm> install DBD::mysql 如果知道模块的名字也可以直接安装
…
ppm> quit

如此就安装完成了。附带说一下,Linux下没有ppm,但是有类似的方式。输入命令行

perl –MCPAN –e shell

然后install DBI; install DBD-mysql,和上面的操作几乎是一样的。

提示:如果是在linux下安装DBD::Mysql模块,需要把mysql的bin目录包含在环境变量PATH中,否则会提示找不到mysql_config文件。mysql一般是安装在/usr/local/mysql下,因此可以通过执行命令行PATH=$PATH:/usr/local/bin/mysql/bin来将此路径加入到环境变量中。

访问XML有几种包可以选择:使用DOM和Simple模块。Simple模块是把XML用Perl的数组方式表示,而DOM是W3C维护的一个基于树的XML文档标准。具体用哪种就看个人的需要了。我使用的是DOM,因此要安装XML-DOM包,方法同上。

芝麻开门

说起编程语言,简单的来说无非就是这样几个必不可少的基本元素:变量、数据、表达式、流程控制语句(包括条件、分支、循环)、函数、对象。具体到语言上,大部分的内容只是表达的形式不同而已。而Perl与C又有什么区别呢?

首先要知道,Perl是一种脚本语言。所谓的脚本,就是没有主函数,从最开始一行一行的按照顺序解释执行(老版Basic不也是如此吗)。因此,尽管把你的思路转化为流程用Perl表达出来吧。

其次,Perl的设计中参考了很多语言的长处,并避免了设计上的缺陷。因此Perl的很多语法你可能都会觉得似曾相识。我把Perl的语法总结了一下,和C语言做了一个简单的对比表格。表格左右两边的语句是C和Perl对应表达同一个功能各自的不同方式。如果读者有C语言的经验,相信看到这个对比可以很快的上手吧?

语法元素 C Perl Perl语法说明
注释 /* comments */ # comments 只支持单行注释
变量
int a, b, c;
char c = 'A';
int x[10];
    
my ($a, $b, $c);
my $c='A';
my @x;
my %h;
    
声明使用my标示
表示值的变量以$开头,表示数组的变量以@开头,表示哈希表的变量以%开头
声明可以省略(不建议)
字符串
char* h1 = "hello\n";
char* h2 = "hello\n";
    
$h1 = "hello\n";
$h2 = 'hello\n';
    
双引号解释内部的\n,而单引号则不解释
一维数组
int arr[10];
arr[0] = 0;
for(i = 0; i < 10; i++) {
    arr[i] = i;
}
    
my @arr = (1, 2, 3, 4, 5);
$arr[0] = 0;
for my $i (1..10) {
    $arr[$i] = $i;
}
for my $a (@arr) {
    print $a;
}
@arr[3..5] = (3..5);
    
数组声明以@标示
动态数组,不需要指定大小
数组下标从0开始
访问数组元素值的时候,要以$开头表示访问的是数值
[3..5]表示数组中下标为3到5之间的元素组成的数组
数组之间可以直接赋值
for循环内必须使用{}不可省略,即使只有一条语句
多维数组
int arr[10][10];
arr[0][1] = 9;
    
my @arr = (1, 2, 3);
$arr[0] = [7, 8, 9];
print $arr[0]->[1]; // 8
    
Perl不直接支持多维数组,但可以以数组引用的方式间接支持。数组引用以[]初始化,用->操作符访问引用实例。例如arr[0]的内容就是一个数组的引用。
指针
char c;
int* x = &c;
c = 'a';
printf( *x );
    
my $c;
my $x = \$c;
$c = 'a';
print $x;
    
\和C中的&类似,意思是取引用
函数指针
void hello() {
    printf(“Hello\n”);
}
void (hi)()=hello;
(p)();
    
sub hello{
    print "Hello\n";
}
my $hi = *hello;
&$hi;
    
&表示调用函数
*取函数地址
不必用括号把参数括起来
调用时的括号也是可选的
条件语句
if (x > 0)
    x = 0; 
else 
    x = 1;
x > 0 ? x = 0 : x = 1;
    
if ($x > 0) {
    $x = 0; 
} else { 
    $x = 1; 
}
$x = 0 if $x > 0;
$x = 0 unless $x <= 0;
$x > 0 ? $x = 0 : $x = 1;
    

  • 基本与C大同小异
  • if 结构可以反转,意义不变,注意前句没有分号
  • unless是if的反义词。顾名思义, unless是“除非”的意思。这里的四个表达方式是等价的
  • 注意第一种方式中,条件部分的圆括号和语句部分的花括号是不可省略

循环语句
for (int i = 0; i < n; i++ )
while ( true ) { ... };
do { ... } while ( true );
    
for (@arry) { print $_; }
for my $key(@ary) { ... }
for my $count (1..10) { ... } 
while (true) { ... }
do { ... } while (true);
    

  • for/while的语法都和C类似
  • for关键字也可以用foreach,意义不变

函数
int max(int x, int y) {
    return x > y ? x : y;
}
int n = max(1, 2);
    
sub max
{
    my ($x, $y) = @_;
    return $x > $y ? $x : $y;
}
my $n = max(1, 2)
    

  • 注意下划线_也是一个合法的变量名。而@_是Perl内置的数组变量,值为当前函数的参数列表
  • ```my ($x, $y)```` 表示声明了一个有两个元素的数组,并将两个元素映射到 $x 和 $y 上
  • ($x, $y) = @_;则表示两个数组之间的复制,@_中对应的元素的值就赋值给了 $x 和 $y .这是一个简便的写法,也可以这样写 my $x = $_[0]; my $y = $_[1];
  • return是可选的,默认返回最后一个表达式的值

语法约束

  • 编译时打开编译器所有的警告选项
  • 使用lint工具

  • perl –w myprogram.pl 打开运行警告开关,如果运行时Perl检查到了可能的错误,会显示警告信息,否则它默认是什么也不提示继续执行

  • #!/usr/bin/perl –w 在代码文件第一行中加入-w选项开关
  • use strict; 使用严格语法约束
  • use warning; 启用警告

  • 运行 编译后直接执行

    • perl myprogram.pl
    • Unix下在代码第一行加入#!/usr/bin/perl,然后给文件加上可执行属性 chmod +x myprogram.pl,之后就可以用./myprogram.pl命令来运行
    • Windows下,安装ActivePerl后,将.pl后缀的文件和perl的解释程序关联起来,因此直接双击文件图标就可以运行

    需要说明的是,在Perl的世界中有一句名言“条条大路通罗马”, 这句话的意思是说同样一件事情Perl允许你用很多种不同的方式去做。因此上表的例子风格是按照C的习惯来写的,并且为了简化起见,只是挑选了与C相似的内容。事实上,Perl包含了很多C没有的东西,例如内置的Hash表、队列、正则表达式、格式定义等等。

    从框架开始

    Perl有很多表达方式,我们可以选择一种自己熟悉、容易理解的方式来写Perl的程序。例如,你是一个经验丰富的C程序员,那么你可以选择以C的风格来写Perl程序。下面是一个小小的样板框架

    #!/usr/bin/perl -w
    use strict;
    

    程序开始的第一行语句,调用main函数

    main();
    

    定义main函数

    sub main
    {
           // some code
    }
    

    在这个框架下面,你几乎可以容易就开始你的Perl开发了。如果需要处理命令行参数,就可以稍微的扩展一下这个框架。

    处理命令行参数

    #!/usr/bin/perl -w
    use strict;
    use Getopt::Std;
    main();
    
    my $configfile;
    
    sub ProcessOptions
    {
           my $VERSION = '1.0.0';
           my $USAGE = "pp.pl [-v | -c configfile]\n";
           my $opts={};
    
           die $USAGE unless( getopts("c:v", $opts) );
           die $VERSION if ($opts->{'v'});
           $configfile=$opts->{'c'} ? $opts->{'c'} : 'config.xml' ;
    }
    
    sub main
    {
           ProcessOptions();
           print $configfile;
    
           // the others code   
    }
    

    实际上,剩余的工作和以往的工作差不多了,编写一个一个的函数,并实现你的业务逻辑。对于你这样一个聪明的程序员来说,学会Perl是一个很容易的事情。

    常见问题

    以我的学习经验来看,在开发的过程中可能有一些常用但是很分散的细节问题会让你感到困惑。

    1、 程序的入口参数怎么取?

    内置数组@ARGV包含了所有的运行参数。可以打印出来看看 print @ARGV;

    2、 函数如何传参数、取参数?

    每个函数内部都有一个内置的数组 @_ ,这个数组的元素就是函数的参数。例如传入的第一个参数就是$[0],第二个是$[1]。唔,如你所见,Perl的函数参数就是C中的动态参数。

    3、 默认变量是什么

    这个可能会把你的头搞晕。有一个内置变量 $_ ,

    4、 显示消息、退出常见的简单写法

    die ‘Error on program’;
    

    也可以在条件不满足的情况下使用

    die ‘Configuration error’ unless($doc->getDocumentElement);
    

    5、 格式化输出

    可以用简单的print语句进行一般的输出操作,如果需要复杂的格式化输出,可以使用printf语句……跟C的用法几乎是一样的。

    printf("pi=%.6f", 355/113);
    

    6、 =>是什么东西?

    在使用Hash表的时候,可以经常看到=>这个符号。例如这样的一个定义:

    my $account={
            'Simon'=> 'simon.jinyu.liu@email.com',
            'Cissy'=> 'cissy@email.com'
    };
    

    其实,=>符号跟逗号,是等价的。Perl里面的Hash表事实上是一个数组,只是把奇数位元素看做是Key(键),而把偶数位的元素看做是Value(值)。

    7、 关于引用的一点说明

    Perl的引用类似C的指针,所谓的引用事实上就是地址。取一个变量的地址用反斜杠”\”操作符,例如 $p=\$x; 那么$p就是一个指向$x变量的指针。要引用指针的值,使用”$”操作符,例如 print $$p; 就是打印$x的值。

    引用不单单可以引用变量,也可以引用数组、HASH表、函数,取函数的地址可以使用*操作符。

    还能做什么

    Perl作为一个功能强大的脚本语言,可以应用在Web 编程、数据库、XML、系统管理、图形图像、自然语言、压缩、加密、邮件系统、软件测试等各个地方。在CPAN上,你可以找到各种各样你所需要的模块支持。例如,你可以:

    • 编写系统管理的脚本
    • 和Apache结合起来,编写CGI程序
    • 编写动态网页
    • 使用Net命名空间下的类编写网络应用程序
    • 使用Authen::Captcha模块实现提交时的验证码的功能
    • 使用Storable模块处理Perl的各种数据结构
    • 使用GD/Image::MagicK模块处理图形
    • 等等…

    资源