Perl简介

一个简单的程序

按历来的习惯,任何一门移植于Unix文化的程序语言入门书都会以“Hello,World”这个程序作为开头。所以,下面是它在Perl里面的写法:

  1. #!/usr/bin/perl
  2. print "Hello, world\n"

假设我们已经将上面两行代码输入到文本编辑器里了(暂且先别管程序各部分是什么意思,我们很快就会讨论到)。一般来说,程序可以用任何文件名保存。Perl程序并不需要用什么特殊的文件名或扩展名命名,甚至都不用扩展名但最好不要用。不过,在某些Unix以外的系统上也许必须使用.plx(代表Perl eXecutable)之类的扩展名。

接下来,你可能还需要告诉系统,该文件是一个可执行的程序(也就是一个命令)。不同的系统需要的操作方式也会有所不同,也许只需要将程序文件存储到某个地方就行了(多数时候在你当前的工作目录也行)。在Unix系统上,你可以使用chmod命令将程序文件的属性改成可执行,如下所示:

  • $ chmod a+x my_program

其中,最前面的美元符号代表命令行提示符(shell prompt),在你的系统上可能会不同。如果执行chmod时习惯用755之类的数值而不是a+x这样的符号来代表权限,效果是相同的。不管用哪种写法,它都能告诉系统,这个文件现在已经是一个可执行的程序了。

现在可以运行这个程序了:

  • $ ./my_program

命令开头的点号与斜线表示要在当前工作目录(current working directory)里查找这个程序,而不是通过环境变量中的PATH指定的路径去搜索。并不一定每次都需要这么写,但在完全了解它们的意义之前,每次执行命令时加上它总归不会有错的。

你也可以显示声明perl命令的路径。如果在Windows系统上,就必须明确给出perl命令所在的路径,因为Windows没有相应的工具程序帮你自动定位:

  • C:\> perl hello_world.pl

如果写完程序第一次运行就能如期运行,那简直就是奇迹。一般多少会有一些低级错误导致的bug,简单修整一下,再运行一次就好。不必每次运行前都用chmod,因为一旦设定文件的可执行属性后,这个状态会一直保留,不会发生变化。

这个简单的程序再Perl 5.10 及其后续版本里还可以有另外一种不同的写法。这次不用print,改用say,它的效果基本相同,但却不需要输入换行符,并且减少了键入次数。由于这是个新特性,而你可能还没安装Perl 5.10,所以使用use V5.10 语句告诉Perl,我们需要引入改版本中的特性:

  1. #!/usr/bin/perl
  2. use v5.10;
  3.  
  4. say "Hello, world!";

这个程序只能在 Perl 5.10 及其后续版本中运行,在本书后续章节中介绍到 Perl 5.10 的新特性时,我们都会明确告诉你这一点,并且使用 use v5.10 语句作为提醒。

一般我们会按照最早引入改特性的版本号来声明。但本书按最新的 5.24 版更新,所以谈到新特性时,会用下面这行提示:

  1. use v5.24;

这里的v可以省略,但如果省略的话,小数点后面就必须写成三位数字的形式:

  1. use 5.024;

本书统一采用带v的写法,这样够简单,也够清楚。

程序里写的是什么?

和其他“形式自由(free-from)”的语言一样,Perl通常可以随意加上空白字符(像空格、制表符与换行符等),使程序代码更易阅读。不过多数Perl程序都会选择使用比较统一的格式标准,本书也不例外。在perlstyle文档中介绍了一些通用的缩排建议(但不是硬性规定)。我们强烈建议并鼓励使用适当的缩排,因为它能有效的增加程序的可读性。缩排工作并不麻烦,许多专业的文本编辑器都能帮你自动处理。此外,好的注释也能改善程序的可读性,从而快速直观地理解程序要做的事。Perl里的注释是从井号(#)开始到行尾结束的部分。

之前程序的第一行其实是个具有特殊意义的注释。在Unix系统里,如果文本文件开头的最前两个字符是#!,那么后面跟着的就是用来执行这个文件的程序路径。在上例中,改程序就是/usr/bin/perl。

事实上,Perl程序里最缺乏可移植性的就是 #! 那行了,因为你必须确定在每台机器上perl解释器是放在什么路径下的。幸好多数情况下不外乎/usr/bin/perl和/usr/local/bin/perl这两种。如果不是,你就得找出系统上perl解释器程序究竟是藏在哪个路径下,然后改用此路径。在Unix系统上,还可以写成下面这种样子,shebang行上执行外部命令自动帮你定位perl解释器的路径:

  1. #!/usr/bin/env perl

如果在所有可查找的目录下都找不到perl解释器,就近请教一下系统管理或是使用同样系统的朋友。但请记住,这仅仅是定位所能找到的第一个perl,而它未必就是你希望使用的那个版本。

在非Unix系统中,传统上第一行会写成 #!perl (其实这也是有用的)。至少,它能让维护人员在着手修正程序时,马上知道这是个Perl程序。

要是 #! 行写错了,shell通常会给出错误信息。它的内容可能会出乎意料,像是“File not found”或“bad interpreter”之类的。这并不是说shell找不到你要运行的程序,而是说找不到程序中指定的 /usr/bin/perl。我们也很想把这个报错信息改的更清楚明确些,但它不是Perl发出的,发出抱怨的是shell

另一个你可能碰到的问题是,你的操作系统不支持 #! 行的写法。这样的操作系统直接把shebang行当作注释,按照自己的约定行为来解释执行文件,所以有可能带来某些意外的结果。

“main”程序由普通的Perl语句构成(我们不是说子程序中的语句,稍后会介绍)。和C或Java之类的语言不同,Perl里面没有所谓的“main”子程序。实际上很多Perl程序是直接写就的,不用子程序。

另外,Perl程序并不需要变量声明的部分,这点和其他语言不同。如果过去你一直习惯声明所有变量,那现在可能会有点不安。不过,这些特性使得我们可以编写“虽然有点难看但马上就能运行”的Perl程序。如果程序的长度只有两行,却将其中一行耗费在声明变量上,似乎不太值得。如果真的想声明变量的话,那也是一件好事情,我们会在第4章里讨论怎么做。

Perl语言的大部分语句都紧接着一个分号。下面的语句我们已经看过好几次了:

  1. print "Hello, world!";

分号的作用是隔离每段语句,而非表示语句的结束。如果后续没有语句,那么不用分号也没有关系:

  1. print "Hello, world!"

我该如何编译Perl程序?

只需要直接运行它就可以。只此一步,perl解释器能一次完成编译和运行这两个动作:

  • $ perl my_program

运行程序时,Perl内部的编译器会先载入整个源程序,将之转化成内部使用的bytecodes,这是一种Perl在内部用来表示程序语法树的数据结构。然后交给Perl的bytecodes引擎执行。所以,如果在第200行有个语法错误,那么在开始运行第二行代码之前,Perl就会报告这个错误。如果你的程序中有一个运行5000次的循环,它只会被编译一次,然后每次循环都以最快的速度执行。除此之外,为了提高易读性,不论你用多少注释和空白,它们都不会影响运行时(runtime)的速度。你甚至可以使用完全由常量组成的算式,它的值只会在程序开始时被计算一次,哪怕它是在某个循环中也只被计算一次,然后在后续的执行中复用计算结果。

不用说,编译是要花时间的+所以如果只是为了要迅捷地完成某个简短任务而去运行一个冗长地包含其他任务代码地Perl程序,会显得效率低下,因为花在编译上地时间可能会比运行的时间还要长。不过Perl编译器的运行速度非常快,通常编译时间只占运行时间的极少部分而已。

不过,有个例外的情况,如果你编写的是CGI脚本,它有可能每分钟会被Web服务器调用运行成百上千次(这样的使用频率非常高。如果像Web上的大多数程序一样,只是每天运行成百上千次的话,就没什么好担心的了)。大多这类程序的运行时间都很短,就需要想办法将程序代码编译后让它驻留在内存中,好让后续的调用跳过编译,直接运行。Apache Web服务器的mod_perl扩展模块或者类似CGI::Fast这样的Perl模块都可以解决这个问题。

要是把编译后的bytecodes存储起来,能否节约编译时间?或者进一步说,能否将bytecodes转化成另一种语言,比如C语言,然后再进行编译?其实以上两种做法在某些情况下都是可行的,不过老实说这么做并没有什么好处,程序不会因此变得更易于使用、维护、调试或安装,甚至(由于某些技术性因素)还会让程序运行得更慢。

走马观花

读到这里,你一定想要看看可以派上实际用场的Perl程序到底是什么样子:

  1. #!/usr/bin/perl
  2. @line = `perldoc -u -f atan2`;
  3. foreach (@line) {
  4.     s/\w<([^>]+)>/\U$1/g;
  5.     print;
  6. }

如果你第一次看到这样的Perl代码,可能会觉得怪异。不过,我们会逐行讲解这段程序,看看它到底做了些什么(下面的介绍会非常简略,毕竟这一节只是“走马观花”。程序里用到的所有功能在后面的章节中都会详加说明)。

第一行是我们介绍过的 #! 行。在你自己的系统上,可能需要修改一下,确认使用的是正确的路径,具体请参考前文所述。

第二行运行了一个外部命令,通过一对反引号(``)来调用。perldoc命令在大部分系统上都有,可以用来阅读Perl及其相关扩展和工具程序的说明文档。

当反引号里的命令执行完毕后,输出结果会一行行依次存储在@lines这个数组变量中。后面一行代码会启动一个循环,依次对每行数据进行处理。循环里的代码是缩排过的。虽然Perl并不强迫你这么做,但好的程序员都会如此要求自己。

循环里的一行代码看起来最恐怖:s/\w([^>]+)/\U$1/g; 此处不会深入讨论细节,它的大概意思就是:对每个包含一对尖括号(<>)的行进行相应的数据替换操作。而在perldoc命令的输出结果里,至少应该有一行符合此操作条件。

至于循环内的第二行代码则来了个大变样,一下子简洁了不少,它直接输出每行的内容(有可能被上面的替换操作修改过)。最后的输出结果看起来应该和perldoc -u -f antan2的执行结果差不多,只是其中出现尖括号的地方有所不同。

就这样,在短短数行代码中,我们调用了别的程序并将它的输出结果放到内存,然后更新内存里的数据,最后输出。很多时候,我们都是用Perl来做这种数据转化工作的。

总结和思考

这一章的确起到了简介的作用,介绍了Perl代码的各个组成部分以及一些特性。目前感觉Perl的“隐式”规则较多,需要后续进一步学习。