本节书摘来自异步社区《UNIX/Linux 系统管理技术手册(第四版)》一书中的第2章,第2.4节,作者:【美】Evi Nemeth , Garth Snyder , Trent R.Hein , Ben Whaley著,更多章节内容可以访问云栖社区“异步社区”公众号查看
2.4 Perl编程
UNIX/Linux 系统管理技术手册(第四版)
Larry Wall发明了Perl语言,它第一种真正伟大的脚本编程语言。它的能耐要比bash大得多,而且编写良好的Perl代码也相当容易阅读。另一方面,Perl没有给开发人员强加太多的风格规范,所以不考虑可读性的Perl代码显得很神秘。Perl也被诟病为只适合写(不适合读)的语言。
我们在这里介绍Perl 5,这个版本成为标准已经有10年了。Perl 6是一个仍处在开发之中的主要版本。参考perl6.org了解详情。
对于系统管理工作来说,Perl或者Python(2.5节开始讨论)都是比传统的编程语言(如C、C++、C#和Java)更好的选择。它们做得更多,但代码行数更少,程序员调试的痛苦更少,而且省却了编译的麻烦。
选择哪种语言通常取决于个人的偏好,或者取决于雇主强加给雇员的标准。Perl和Python都提供了由用户群编写的模块库和语言扩展库。Perl存在的年头更长,所以它提供的库长尾效应更为明显[译者注:即功能繁多影响力不大的库数量更庞大,累积起来的效应也更大]。不过,对于常见的系统管理任务而言,两者的支持库大致上等价。
Perl的口号是“条条大路通罗马。”要记住本节读到的大多数例子都能用别的方法来实现。
Perl的语句用分号分隔1。注释以一个井号(#)开头,并且一直到这一行的结尾。语句块用花括号括起来。下面是一个简单的“hello,world!”程序:
\#!/usr/bin/perl
print "Hello, world!\n";
和bash程序一样,必须用chmod +x将这个文件改为可执行,或者直接调用Perl的解释程序执行它。
$ chmod +x helloworld
$ ./helloworld
Hello, world!
Perl脚本中的代码行都不是shell命令;它们是Perl代码。bash可以让用户把一系列命令组合起来,把它叫做脚本,但Perl和bash不一样,除非让Perl去看外面的世界,否则Perl自己不会看。也就是说,Perl提供了许多和bash一样的惯例,如使用撇号来获得一条命令的输出结果。
2.4.1 变量和数组
Perl有3种基本数据类型:标量(也就是说,像数和字符串这样的一元量)、数组和哈希(hash)。哈希也叫做关联数组。变量的类型总是一目了然,因为它体现在变量名上:标量的变量以$开头,数组变量以@开头,而哈希变量以%开头。
在Perl语言里,“列表(list)”和“数组(array)”这两个术语经常混用,不过或许更准确的说法是,列表是一系列的值,而数组是能够保存这样一个列表的变量。数组里的各个元素都是标量,所以和普通的标量变量一样,它们的名字都以$开头。数组下标从零开始,数组@a里元素的最大下标是$#a。这个值加1就等于数组的长度。
数组@ARGV保存着该脚本的命令行参数。可以像访问其他任何数组那样来访问它。
下面的脚本展示了数组的用法:
#!/usr/bin/perl
@items = ("socks", "shoes", "shorts");
printf "There are %d articles of clothing.\n", $#items + 1;
print "Put on ${items[2]} first, then ", join(" and ", @items[0,1]), ".\n";
输出为:
$ perl clothes
There are 3 articles of clothing.
Put on shorts first, then socks and shoes.
仅仅在这几行代码中,看点就很多。冒着分散我们注意力的风险,我们在所举的每个Perl例程中都包含了几种常见的习惯用法。在每个例子之后的文字中,我们都会阐述其中的窍门。如果仔细阅读这些例子(不要胆怯,它们都不长!),在看完本章的内容之后,就会对Perl最常碰到的形式有了一些经验。
2.4.2 数组和字符串文字
在本例中,首先要注意(…)创建了一个列表。这个列表里的每个元素都是字符串,它们由逗号隔开。创建好这个列表之后,就把它赋值给变量@items。
Perl不严格要求所有的字符串都要用引号引起来。在这个具体的例子里,没有引号也能给@items赋初值。
@items = (socks, shoes, shorts);
Perl把这些没有用引号引起来的字符串称为“裸单词(bareword)”,它们就按上次访问的方式来解释。如果用任何其他方式解释都没有意义,Perl就尝试把它按一个字符串来解释。在有限的几种情况下,这样解释有意义,并且能让代码保持整洁。不过,这里不一定正好就是上述几种情况之一。即使自己能一直保持用引号把字符串引起来,也要有所准备,去分析别人写的没有用引号引起来的代码。
要初始化这个数组,更Perl化的办法是用qw(指quote words的缩写)操作符。它实际上是把字符串用引号引起来的一种形式,而且和Perl里大多数被引号引起来的实体一样,可以选择自己的限定符。下面的形式
@items = qw(socks shoes shorts);
是最传统的办法,但是它容易误导人,因为qw之后的部分不再是一个列表了,它实际上是一个用空白分隔字符串形成的一个列表。下面的这个版本的写法
@items = qw[socks shoes shorts];
也能用,而且对于真正要做什么来说,这种写法可能还更正确一点儿。注意逗号没了,因为qw已经包括了它们的功能。
2.4.3 函数调用
print和printf都能接受任意数量的参数,这些参数由逗号分隔。但是join(…)看上去更像是某种函数调用;它和print和printf有怎样的不同呢?
实际情况并非如此;print、printf和join都是普通的函数。如果不会引起歧义,Perl允许省略函数调用的圆括号,所以两种调用形式(带括号和不带括号)都很常用。在上面例子的print那句中,圆括号把join的参数同print的参数给区分开了。
我们可以说表达式@items[0,1]必定要按某种列表来算,因为它以@开头。这实际上是一个“数组段”或者叫子数组,0和1两个下标列出了在这个数组段里包含的数组元素的索引。Perl在这里也能接受一个范围值,就像其等价表达式@items[0..1]里出现的那样。这里也能接受单个数值下标:@items[0]是一个列表,里面只有一个标量,即字符串“socks”。在这种情况下,它等价于("socks")这个常量。
Arrays are automatically expanded in function calls, so in the expression
函数调用会自动扩展数组,如下面的表达式中
join(" and ", @items[0,1])
join接收到3个字符串参数:“and”、“socks”和“shoes”。它把第二个及后面的参数连起来,在两个参数之间插入第一个参数。结果是“socks and shoes”。
2.4.4 表达式里的类型转换
在printf那一行,$#items + 1计算得到数字3。由此看来,$#items是一个数值,但这并不是这个表达式要按数值计算的原因;"2" + 1也是可以的。关键在于运算符+,它总是暗指算术运算。它把自己的参数转为数字,并计算得到数值结果。类似地,点运算符(.)把两个字符串连接起来,它根据需要转换自己的操作数:"2" . (12 ** 2)得到“2144”。
2.4.5 字符串表达式和变量
和bash里的情况一样,双引号引起来的字符串可以进行变量扩展。和bash里一样的还有,如果必要,可以用花括号把变量名括起来,如${items[2]},从而避免歧义(这里的花括号只用于演示;它们不是必须的)。$暗示这个表达式应该按标量来算。@items是数组,但是它的任何一个元素都是标量,起名字的习惯就反映出了这一事实。
2.4.6 哈希
哈希(也称为关联数组)表示一组“键/值”对。可以把一个哈希当作下标(键)是任意标量值的数组;它们不一定是数字。但在实际中,数字和字符串都是常用的键。
哈希变量的第一个字符是%(例如,%myhash),但是和在数组中的情况一样,哈希里的单个值都是标量,所以也以$开头。哈希的下标用花括号而不是方括号括起来,例如$myhash{'ron'}。
哈希是系统管理员的一个重要工具。系统管理员编写的几乎每个脚本都会用到它们。在下面的代码中,我们会读取一个文件的内容,按照/etc/passwd里的规则分析它,然后用分析得到的若干项结果构造一个叫做%names_by_uid的哈希。这个哈希中每一项都是用户名和它对应的UID。
#!/usr/bin/perl while ($_ = <>) {
($name, $pw, $uid, $gid, $gecos, $path, $sh) = split /:/;
$names_by_uid{$uid} = $name;
}
%uids_by_name = reverse %names_by_uid;
print "\$names_by_uid{0} is $names_by_uid{0}\n";
print "\$uids_by_name{'root'} is $uids_by_name{'root'}\n";
和上面的那个示例脚本一样,我们在这几行代码里也加入了几点新思想。在开始介绍这几处细节之前,先看一下这个脚本的输出:
$ perl hashexample /etc/passwd
$names_by_uid{0} is root
$uids_by_name{'root'} is 0
while ($ = <>)这条语句一次一行读取输入,把它赋值给名为$的变量;和C语言一样,整个赋值语句的值是等号右边的值。当碰到输入的结尾时,<>返回一个出错值,然后结束循环。
Perl对<>的解释为,检查命令行看是否在那里给出了任何文件。如果提供了文件,那么它就依次打开每个文件,然后在循环里运行这个文件的内容。如果没有在命令行给出任何文件,那么Perl就考虑从标准输入获得输入。
在循环体内,把split返回的值赋值给一系列变量,split是个函数,它用传给它的正则表达式作为域分隔符,对输入字符串进行分隔。这里的正则表达式用斜线来界定;这只是另一种形式的引用符号,这种形式的引用专门用于正则表达式,但和双引号的解释很类似。我们同样也可以写为split ':'或者split ":"。
这个split要用冒号分割的字符串到底是什么,没有明确地指定。当split没有第二个参数的时候,Perl就认为要分割$_的值。说实话,即使这个匹配模式(即用冒号做分隔符)也是可有可无的;默认用空白做分隔符,但却会忽略开头的所有空白。
不过稍等一下,还要多说一点。甚至回到循环一开始的地方,给$_赋值的操作也是不必要的。如果简单地写
while (<>) {
那么Perl会自动把每行都保存在$里。用户不必明确地去访问保存输入行的变量,就可以处理这些行。把$用作默认操作数的做法很常见,只要在$或多或少有意义的地方,Perl都会允许用$。
在获得passwd文件内各个域值的多重赋值语句里
($name, $pw, $uid, $gid, $gecos, $path, $sh) = split /:/;
等式左边出现的列表创建了split的“列表上下文”,告诉split返回结果是由所有的域构成的一个列表。如果是给一个标量赋值,例如,
$n_fields = split /:/;
split则运行在“标量上下文”里,只返回它找到的域的个数。通过使用wantarray函数,用户自己编写的函数也能区分标量上下文和列表上下文。该函数在列表上下文中返回一个真值,而在标量上下文中返回一个假值,在空(void)上下文里则返回一个不定值。
The line
下面这行代码
%uids_by_name = reverse %names_by_uid;
也有一些深意。列表上下文里的哈希(这里是reverse函数的一个参数)算成(key1, value1, key2, value2, …)形式的一个列表。reverse函数颠倒该列表的次序,得到(valueN, keyN, …, value1, key1)。最后,给哈希变量%uids_by_name赋值的时候把这个列表按(key1, value1, …)这样转换,因此得到了颠倒的索引。
2.4.7 引用和自动生成
虽然这两方面都是高级话题,但是如果我们不提一下它们的话,那就是我们的工作怠慢了。这里给出它们的简短总结。数组和哈希只保存标量,但用户经常想在其中再保存别的数组和哈希。例如,回到我们前面分析/etc/passwd文件的那个例子,用户可能想要把passwd文件里每一行所有的域都保存到一个用UID来索引的哈希结构里。
虽然不能直接保存数组和哈希,但是却可以保存对数组和哈希的引用,因为引用本身是标量。只要在变量名之前加上一个反斜线(例如,@array),或是用引用数组或引用哈希的语法,就可以建立对数组或者哈希的引用。例如,分析口令的循环就可以变成下面的样子:
while (<>) {
$array_ref = [ split /:/ ];
$passwd_by_uid{$array_ref->[2]} = $array_ref;
}
尖括号返回对一个数组的引用,这个数组里保存有分割后的结果。$array_ref->[2]的记法引用UID域,即$array_ref所指数组中的第三个成员。
这里不能用$array_ref[2],因为我们没有定义@array_ref这个数组;$array_ref和@array_ref是不同的变量。再进一步说,如果在这里错误地用了$array_ref[2],那么也不会得到出错消息,因为@array_ref是一个完全合法的数组名;只是不能给它赋值而已。
缺少报警消息似乎是个问题,但它可以说是Perl最好的特性之一,这个特性叫做“自动生成(autovivification)”。因为变量名和引用语法总是会清楚地表明要访问的数据结构,所以就不用手工随时创建任何数据结构了。只要进行最低可能层面的赋值操作,就会自动生成中间结构。例如,只用一次赋值操作,就可以创建一个指向数组的哈希结构,数组的内容又是指向哈希的引用。
2.4.8 Perl语言里的正则表达式
用=~操作符把字符串“绑定”到正则表达式上,就可以在Perl里使用正则表达式了。例如,下面这一行代码
if ($text =~ m/ab+c/) {
检查保存在$text里的字符串,看是否能够匹配正则表达式ab+c。要对默认字符串$_进行操作,只要省略掉变量名和绑定操作符即可。实际上,还可以省去m,因为默认就是执行匹配操作:
if (/ab+c/) {
替换操作也和匹配操作类似:
$text =~ s/etc\./and so on/g; # Substitute text in $text, OR
s/etc\./and so on/g; # Apply to $_
我们插入了一个g选项,用“and so on”替换所有出现的“etc.”,而不是只替换第一次出现的“etc.”。其他常见的选项有忽略大小写的i、用点号(.)匹配换行的s,还有m,即用^和$匹配各行行首和行尾,而不只是要搜索的文本的开头和结尾。
下面这个脚本演示了另外两个要点:
#!/usr/bin/perl
$names = "huey dewey louie";
$regex = '(\w+)\s+(\w+)\s+(\w+)';
if ($names =~ m/$regex/) {
print "1st name is $1.\n2nd name is $2.\n3rd name is $3.\n";
$names =~ s/$regex/\2 \1/;
print "New names are \"${names}\".\n";
} else {
print qq{"$names" did not match "$regex".\n};
}
该脚本的输出为:
$ perl testregex
1st name is huey.
2nd name is dewey.
3rd name is louie.
New names are "dewey huey".
这个例子展示了由//括起来的变量怎样做扩展,这样一来,正则表达式就不必是一个固定的字符串了。qq是双引号操作符的另一种写法。
在执行一次匹配或者替换操作之后, $1、$2等变量的内容就和正则表达式里括号中捕获的内容相对应。这些变量的内容在做替换操作时也能用,此时用1、2等来引用它们。
2.4.9 输入和输出
打开一个文件执行读或者写操作时,就要定义一个“文件句柄”来标识这个通道。在下面的例子里,INFILE是/etc/passwd的文件句柄,而OUTFILE是关联到/tmp/passwd的文件句柄。while语句的循环条件是,它和我们已经见过的<>很相似,但它专指一个文件句柄。这条语句读取文件句柄INFILE中的每一行,直到文件结尾,这时while循环结束。每行的内容都放入$_变量。
#!/usr/bin/perl
open(INFILE, "</etc/passwd") or die "Couldn’t open /etc/passwd";
open(OUTFILE, ">/tmp/passwd") or die "Couldn’t open /tmp/passwd";
while (<INFILE>) {
($name, $pw, $uid, $gid, $gecos, $path, $sh) = split /:/;
print OUTFILE "$uid\t$name\n";
}
如果成功打开了这个文件,则open返回一个真值,从而绕过对die子句的判断(让这个子句不必执行)。Perl的or操作符和||(Perl也有这个操作符)的作用很像,但是优先级更低。如果要特意先对操作符左边的全部内容做判断,然后Perl才关注失败的结果,那么采用操作符or一般是更好的选择。
Perl用来指定如何使用每个文件(读?写?追加?)的语法和shell一模一样。还可以用像"/bin/df |"这样的“文件名”来打开和shell命令联系的管道。
2.4.10 控制流程
下面的例子用Perl重新实现了一个bash脚本,我们早先用后者来验证命令行参数是否有效。读者可以参照2.2.3节里该脚本的bash版本。注意,Perl的if语句结构没有then这个关键字,也没有终结关键字,它只是一个用花括号括起来的语句块。
还可以在单个语句的后面追加if子句(或者unless子句,它是if子句的否定版本),使得该语句有条件地执行。
#!/usr/bin/perl
sub show_usage {
print shift, "\n" if scalar(@_);
print "Usage: $0 source_dir dest_dir\n";
exit scalar(@_) ? shift : 1;
}
if (@ARGV != 2) {
show_usage;
} else { # There are two arguments
($source_dir, $dest_dir) = @ARGV;
show_usage "Invalid source directory" unless -d $source_dir;
-d $dest_dir or show_usage "Invalid destination directory";
}
在这个例子中,有两行代码使用了Perl的单目操作符-d,用来判断$source_dir和$dest_dir是否为目录。第二种形式(-d操作符在行首)有优势,它把实际的判断语句放在行首,这个位置最显眼。不过,用or来表示“否则”的意思有点儿难懂;有些读到这一代码的人可能会发现它容易让人误会。
按标量上下文(本例中由标量运算符来指出)取数组变量的值,返回的是该数组内元素的个数。这个值比$#array的值正好多1;在Perl里,有不止一种方法可以得到这个值。
Perl的函数从名为@_的数组里获得它们的参数。用shift操作符是访问这些参数最常用的方法,shift操作符删除参数数组里的第一个元素,并返回它的值。
这个版本的show_usage函数接受一则要打印的出错消息,但也可以不提供这个出错消息。如果提供了一则出错消息,那么还可以提供一个特殊的退出码。三目操作符?:计算其中第一个参数的值;如果值为真,那么返回结果就是第二个参数;否则就返回第三个参数。
和bash里一样,Perl也有一种专门的“else if”条件,但是它的关键字是elsif而不是elif(对于bash和Perl两种语言都用的人来说,这些有意思的小差别要么让人头脑聪颖,要么让人抓狂)。
如表2.5所示,Perl的比较运算符正好和bash的相反;字符串比较用文本运算符,而数值比较用传统的代数表达式。读者可以和2.2.7节的表2.2进行比较。
在Perl里,可以使用表2.3中除-nt和-ot之外的所有文件测试操作符,-nt和-ot只有bash才支持。
和bash一样,Perl也有两种类型的循环。比较常见的循环形式是通过一个明确的参数列表。例如,下面的代码通过一个动物的列表来循环,每行打印一个动物的名字。
@animals = qw(lions tigers bears);
foreach $animal (@animals) {
print "$animal \n" ;
}
也可以采用更传统的C风格的循环:
for ($counter=1; $counter <= 10; $counter++) {
printf "$counter ";
}
我们给出了传统的for和foreach两种写法,但实际上,它们两个在Perl里是相同的关键字,可以根据自己的偏好选用其中任何一个。
在Perl 5.10(2007年)版之前都没有明确的case或者switch语句,但可以用几种办法取得相同的效果。用多层嵌套的if语句显然是一种办法,但除了这种不太好的做法之外,另一种可能的方法是用一条for语句设置$_的值,然后提供一种上下文,让last语句可以从中跳出来:
for ($ARGV[0]) {
m/^websphere/ && do { print "Install for websphere\n"; last; }; m/^tomcat/ && do { print "Install for tomcat\n" ; last; }; m/^geronimo/ && do { print "Install for geronimo\n"; last; };
print "Invalid option supplied.\n"; exit 1;
}
用多个正则表达式和$_里保存的参数进行比较。匹配不成功的话,就绕过&&并直接落入下一个测试条件。只要匹配了一个正则表达式,那么就执行相应的do语句块。然后由last语句从for语句块里立即跳出。
2.4.11 接受和确认输入
下面的脚本把我们在前面碰到的许多Perl结构都结合了起来,包括例程(函数)、后缀形式的if语句,以及for循环。这个程序本身只是主函数get_string的一个封装程序,get_string是一个检查输入有效性的通用例程。这个例程提示输入一个字符串,删除结尾的所有换行符,然后核对该字符串是否为空。空字符串会提示重新输入,三次之后脚本就放弃尝试。
#!/usr/bin/perl
$maxatt = 3; # Maximum tries to supply valid input sub get_string {
my ($prompt, $response) = shift;
# Try to read input up to $maxatt times
for (my $attempts = 0; $attempts < $maxatt; $attempts++) {
print "Please try again.\n" if $attempts;
print "$prompt: ";
$response = readline(*STDIN);
chomp($response);
return $response if $response;
}
die "Too many failed input attempts";
}
# Get names with get_string and convert to uppercase
$fname = uc get_string "First name";
$lname = uc get_string "Last name";
printf "Whole name: $fname $lname\n";
这个脚本的输出为:
$ perl validate
First name: John Ball
Last name: Park
Whole name: JOHN BALL PARK
在函数get_string和for循环中,都用my操作符创建有局部作用域的变量。在默认情况下,Perl中的变量都是全局变量。
对get_string里的局部变量列表进行初始化的时候,只从该函数的参数数组中获得了一个标量。对于初始化列表中的变量,如果没有获得相应的值(本例中是$response),那么就保持未定义的状态。
传给函数readline的*STDIN是一个“类型通配(typeglob)”,这在语言设计上是一个让人讨厌的缺点。最好不要太深入地去搞清楚它的确切含义,以免搞得自己头大。简单解释说,就是Perl的文件句柄不是一流的数据类型,所以一般必须在它们的名字之前加一个星号,才能当做参数传给函数。
在给$fname和$lname赋值的语句里,uc(代表“convert to uppercase”,即转为大写)和get_string两个函数调用都不带括号。因为在一条语句里不可能出现混淆,所以这样写没问题。
2.4.12 Perl用作过滤器
不通过脚本也能用Perl,只要在命令行写单独的表达式就行。这是做快速文本转换的一种好方法,这种方法几乎取代了比较老的过滤器程序,如sed、awk和tr。
用命令行选项-pe可以对STDIN循环处理,对每行做一次简单匹配,然后打印结果。例如,下面这条命令
ubuntu$ perl -pe 's#/bin/sh$#/bin/bash#' /etc/passwd root:x:0:0:root:/root:/bin/bash daemon:x:1:1:daemon:/usr/sbin:/bin/bash
…
把/etc/passwd里每行末尾的/bin/sh替换为/bin/bash,然后把转换后的passwd文件送到STDOUT。看到用斜线作为定界符(例如,s/foo/bar/),配合文本替换操作符一起使用,这种形式对读者来说可能更习惯,但是Perl允许用任何字符。在这里,搜索文本和替换文本都带有反斜线,所以用#作为定界符更简单。如果用成对的定界符,那么必须用4个,而不是通常的3个,例如,s(foo)(bar)。
Perl的-a选项打开了自动分隔模式,这种模式把输入行分成若干个域,保存在名为@F的数组里。默认的域分隔符是空白,但可以用-F选项设置另一种分隔符模式。
自动分隔模式和-p或者-n(-p的变体,它表示不自动打印)配合使用很方便。例如,下面的前两条命令用perl -ane把df两种不同形式的输出进行分隔。第三行命令接着执行join操作,把两组结果按Filesystem域拼接到一起,生成的组合表里包括两个df输出版本中的所有域。
suse$ df -h | perl -ane 'print join("\t", @F[0..4]), "\n"' > tmp1 suse$ df -i | perl -ane 'print join("\t", @F[0,1,4]), "\n"' > tmp2 suse$ join tmp1 tmp2
不用临时文件的脚本如下:
#!/usr/bin/perl
for (split(/\n/, ‘df -h‘)) {
@F = split;
$h_part{$F[0]} = [ @F[0..4] ];
}
for (split(/\n/, ‘df -i‘) {
@F = split;
print join(“\t”, @{$h_part{$F[0]}}, $F[1], $F[4]), “\n”;
}
真正厉害的地方在于,可以把-i和-pe连起来用,就地编辑文件;Perl把文件读进来,提供文件里要编辑的那些行,然后再把结果保存回原来的文件。可以给-i提供一个模式,告诉Perl怎样备份每个文件原来的版本。例如,-i.bak把passwd文件备份为passwd.bak。要小心——如果没有提供一个备份模式,那么就根本得不到备份了。注意,在-i和后缀名之间没有空格。
2.4.13 Perl的附加模块
位于cpan.org的CPAN(Comprehensive Perl Archive Network,综合Perl存档网络)是一个Perl语言的大宝库,里面有用户贡献的第三方库。用cpan命令安装新的模块非常简单,就像把yum或者APT这样的包管理软件用到Perl模块上一样。如果在Linux系统上,那么检查一下,看该Linux发行版本是否把要找的模块已经放在软件包里,成为一个标准的功能——在系统层面只安装一次,然后让系统以后负责该模块的升级,这种做法更简单。
在没有cpan命令的系统上,可以运行perl -MCPAN -e shell,从另一条途径达到同样的效果:
$ sudo perl -MCPAN -e shell
cpan shell -- CPAN exploration and modules installation (v1.9205)
ReadLine support available (maybe install Bundle::CPAN or Bundle::CPANxxl?)
cpan[1]> install Class::Date
CPAN: Storable loaded ok (v2.18)
CPAN: LWP::UserAgent loaded ok (v5.819) CPAN: Time::HiRes loaded ok (v1.9711)
… several more pages of status updates …
用户还可以把Perl模块安装到自己的主目录里,供个人使用,但是这个过程并不简单。我们推荐采用一种自由的策略,把CPAN的第三方模块安装到系统层面上;Perl社区提供了一个发布模块的中心点,放开代码供人们检查,贡献模块的人都要给出自己的名字。Perl模块不会比任何别的开源代码更危险。
为了获得更好的性能,许多Perl模块都用到了C写的组件。安装这样的模块就会涉及去编译这些组件,所以需要一个完整的开发环境,其中要包括C编译器和一整套库。
和大多数语言一样,Perl程序里最常见的错误是,重复实现已经由社区编写的模块所提供的功能2。养成习惯,在解决任何Perl的问题时首先访问CPAN,这样做会节省开发和调试的时间。