Perl

perl 和 Ruby 的语法很像

perl 正则表达式 强大,文本处理能力强,现在主要用于文本处理,可调用shell脚本

perl 语法怪异,很难记住所有的符号

学习地址:http://www.runoob.com/perl/perl-tutorial.html

Perl语言是一门高级解释型动态语言,它的许多数据类型是运行时才确定的,并且经常和PHP和 Python相提并论。Perl从古老的Shell脚本语言中借鉴了许多语法特性,因为被过度使用的各种奇怪符号而声名狼藉,而且许多代码即使借助 Google的搜索功能都不能完全看明白。许多来自 Shell 的语言特性使之成为一门好用的胶水语言:
将其他语言和脚本连接在一起共同工作。

语言非常适合处理文本,并生成更多的文本。Perl语言应用广泛,流行,可移植性极佳,而且有良好的社区支持。Perl 语言的设计哲学是:"每个问题都有许多解决方式"(TMTOWTDI)( 与之相反,Python的设计哲学是:每个问题应该只有一种,而且只有一种明确的,最好的解决方式)。 

Perl有令人沮丧的地方,但同时也有许多奇妙的特性。从这一点来看,它同其他任何一种曾经有过的编程语言一样。

这篇文章只是提供一些知识,并不是什么宣传广告,目标读者只是针对像我这样的人:

  • 厌恶 http://perl.org 上面学术性的文档,那些东西只会长篇累牍的讲述一些永远用不到的边缘问题。
  • 我只想通过一些通用规则和实例快速了解那些从Larry Wall的角度永远都不关心的基础编程问题
  • 学些能帮我找到一份工作的基础知识

这篇文档的目的是用短的不能再短的形式来讲.

研究初探

以下声明针对整个文档:“这并不是绝对的,实际情况要复杂的多”。你如果发现一个严重错误,请告诉我。但我保留对孩子们说错话的权利。

在本文档中,我在实例中打印状态,输出数据,但没有明确的追加换行符。这样做是为了避免我发狂,让我能集中精力在实例中字符串的输出,我相信这是更重要的。在许多例子中,实际的输出可能是 "alotofwordsallsmusheduptogetherononeline". 请不要在意。

Hello world

一个 Perl 脚本就是一个后缀为 .pl 的文本文件。

这就是 helloworld.pl 的全文:

1 use strict;
2 use warnings;
3 print "Hello world";

Perl 脚本被 Perl 的解释器来解释运行,perl 或者 perl.exe:

1 perl helloworld.pl [arg0 [arg1 [arg2 ...]]]

说实话, Perl的语法非常宽容,他可以替你预测一些模糊的不知所云的代码的行为,我实在不想讲这些东西,因为你们应当竭力避免这么做。

避免这么做的方法就是将 'use strict; use warnings' 放置在每一个脚本或模块的最前面。'use foo' 这样的语句是编译提示,编译提示是给 Perl.exe 的信号,在程序运行前,对解释器初始化语法验证规则施加影响。在代码运行的时候,解释器将忽略这些东西。' #'
符号是一个注释的开始。注释的结束是到行的末尾。Perl 没有针对块的注释语法。

变量

Perl的变量有三种类型:标量(scalar),数组(array)和哈希(hash)。每种类型有它自己的记号,分别是¥,@和%。变量用my声明,保存到封闭的块(enclosing block)或者文件(file)的结尾。

标量变量

标量变量可以储存:

  • undef(和Nonein Python,nullin PHP一致)
  • 一个数字(Perl不区分整数(integer)和浮点(float))
  • 字符串
  • 引用任何其他变量。
my $undef = undef;
print $undef; # prints the empty string "" and raises a warning

# implicit undef:
my $undef2;
print $undef2; # prints "" and raises exactly the same warning
my $num = 4040.5;
print $num; # "4040.5"
my $string = "world";
print $string; # "world"

(引用的用法稍后讲述。)

用.操作符实现字符串的链接(和PHP一样):

print "Hello ".$string; # "Hello world"

布尔值(Booleans)

Perl 没有布尔数据类型。只有如下几种值才能在 if 判断语句中返回 'false' :

  • undef
  • 数字 0
  • 字符串 ""
  • 字符串 "0"

Perl 文档中经常提到函数在某些情况下会返回 'true' 或 'false'. 实际上,函数通常用 'return 1' 来返回真,用返回空字符串 '' 来表示返回假。

弱类型(Weak typing)

断定一个标量中存储的到底是一个数字或是一个字符串,这是不可能的。更进一步说,根本没必要去确定这个问题。一个标量的行为到底像一个数字或字符串完全取决于它的操作符。当做一个字符串用的时候,标量表现的就是一个字符串,而当做一个数字来用时,它又变成了数字。(如果不可能转换就会有个警告):

my $str1 = "4G";
my $str2 = "4H";

print $str1 .  $str2; # "4G4H"
print $str1 +  $str2; # "8" with two warnings
print $str1 eq $str2; # "" (empty string, i.e. false)
print $str1 == $str2; # "1" with two warnings

# The classic error
print "yes" == "no"; # "1" with two warnings; both values evaluate to 0 when used as numbers

座右铭就是一直用相应的情况下使用合适的操作符。在标量比较中,字符串和数字分别有两套不同的操作符:

1 # 数字操作符:    <,  >, <=, >=, ==, !=, <=>, +, *
2 # 字符串操作符:  lt, gt, le, ge, eq, ne, cmp, ., x

数组型变量(Array variables)

一个数组型变量是以 0 为索引开始的一组标量列表。在 Python 中被成为 list, 在 PHP 中被称为 array. 一个数组的声明使用一个被括号包围的标量列表:

1 my @array = (
2   "print",
3   "these",
4   "strings",
5   "out",
6   "for",
7   "me"# 后面的逗号是允许的
8 );

你不得不使用一个美元符号前缀访问一个数组的元素,因为被取出的元素不再是数组,而是一个标量:

1 print $array[0]; # "print"
2 print $array[1]; # "these"
3 print $array[2]; # "strings"
4 print $array[3]; # "out"
5 print $array[4]; # "for"
6 print $array[5]; # "me"
7 print $array[6]; # 返回 undef, 打印 "" 并提示一个警告

你们可以使用负数索引来检索从后往前的元素:

1 print $array[-1]; # "me"
2 print $array[-2]; # "for"
3 print $array[-3]; # "out"
4 print $array[-4]; # "strings"
5 print $array[-5]; # "these"
6 print $array[-6]; # "print"
7 print $array[-7]; # 返回 undef, p打印 "" 并提示一个警告

在一个 $var 和 @var 中包含的 $var[0] 之间并没有什么关系, 但这可能让阅读的人迷糊,因此应当避免这么做。

获取数组的长度:

1 print "This array has ".(scalar @array)."elements"#
"这个数组有6个元素"
2 print "The last populated index is ".$#array;   # "最后一个索引的号码是 5"

Perl 脚本捕获的命令行参数被保存在内置的数组变量 @ARGV 中。

1 print "Hello $string"# "Hello world"
2 print "@array";        # "print these strings out for me"

小心,有天你会把一个电子邮件地址放置到一个字符串中,例如 "jeff@gmail.com". 这将导致Perl将一个数组型变量 @gmail 内插到字符串中,而且无法找到这个变量,并引起一个运行错误。这个问题可以通过两种方式解决:转义这个变量前置符,或使用单引号而不是双引号来引起字符串。

1 print "Hello \$string"# "Hello $string"
2 print 'Hello $string';  # "Hello $string"
3 print "\@array";        # "@array"
4 print '@array';         # "@array"

哈希型变量

哈希型变量是按照字符串来进行索引的列表。在 Python 中称为字典,而在 PHP 中被称为关联数组。

my %scientists = (
	"Newton"   => "Isaac",
	"Einstein" => "Albert",
	"Darwin"   => "Charles",
);

请注意散列的声明和数组何其相似。实际上,双箭头符号 => 也叫胖逗号,因为他只是逗号的同义符号。一个散列由偶数个元素列表组成,所有偶数位置的元素都将被保存为字符串。

再一次,你不得不使用一个美元符号前缀来获取一个散列的值,因为被取出的值不再是一个散列,而是一个标量。

print $scientists{"Newton"};   # "Isaac"
print $scientists{"Einstein"}; # "Albert"
print $scientists{"Darwin"};   # "Charles"
print $scientists{"Dyson"};    # returns undef, prints "" and raises a warning

请注意这里使用的是大括号。同样,一个标量 $var 和一个 包含 $var{"foo"} 内容的 %var 没有任何冲突。

你也可以直接将一个偶数个元素的数组转换成散列,这些元素将交错的成为键和值(转换回来也同样容易).

my @scientists = %scientists;

然而,不像数组,散列的键没有特定的保存顺序。键值在内部是以一种非常高效的方式保存,因此要注意返回的数组的顺序会被成对的重新排列:

print "@scientists"; # something like "Einstein Albert Darwin Charles Newton Isaac

回顾一下,你必须使用方括号来从数组中检索一个值, 但必须使用大括号来从散列中检索一个值。方括号实际上是一个数值操作符,而大括号是一个的字符串操作符。事实上,查询索引是一个数字或是字符串都没有关系:

my $data = "orange";
my @data = ("purple");
my %data = ( "0" => "blue");

print $data;      # "orange"
print $data[0];   # "purple"
print $data["0"]; # "purple"
print $data{0};   # "blue"
print $data{"0"}; # "blue"<span style="line-height: 17.2727px;"> </span>

列表(list)

在 Perl 中,列表 跟数组或哈希表是有区别的。下面是一些列表:

(
	"print",
	"these",
	"strings",
	"out",
	"for",
	"me",
)

(
	"Newton"   => "Isaac",
	"Einstein" => "Albert",
	"Darwin"   => "Charles",
)

列表不是变量。列表是一种可以被分配到数组或哈希表变量中的短小的。这也是为什么声明数组和哈希表变量的语法相同的原因。虽然有很多时候,“列表”和“数组”可以通用,但也要看到很多时候两者之间的形式有着些许的不同,以及行为的区别。

好,记住 => 只是 , 的另一种形式,让我们看看下面的例子:

("one", 1, "three", 3, "five", 5)
("one" => 1, "three" => 3, "five" => 5)

使用=>的目的是表明下面列表中的一些是数组声明,而另一些是哈希表声明。但他们并没有声明任何东西。他们只是列表。相同的列表。下面也是:

()

上面没有表明任何东西。列表可以用来声明一个空的数组或哈希表,显然 Perl 的解释器无法告诉你它究竟是什么。一旦你理解了 Perl 的这种奇怪,你就能够理解列表值不能嵌套是真的了,试试下面的代码:

my @array = (
	"apples",
	"bananas",
	(
		"inner",
		"list",
		"several",
		"entries",
	),
	"cherries",
);

Perl 永远都无法了解 ("inner", "list", "several", "entries") 到底是内部数组还是内部哈希表。Perl 会将其展开成为一个长列表,而非上面提到的内部数据。

print $array[0]; # "apples"
print $array[1]; # "bananas"
print $array[2]; # "inner"
print $array[3]; # "list"
print $array[4]; # "several"
print $array[5]; # "entries"
print $array[6]; # "cherries"

无论用的是不是逗号,都是这样:

my %hash = (
	"beer" => "good",
	"bananas" => (
		"green"  => "wait",
		"yellow" => "eat",
	),
);

# The above raises a warning because the hash was declared using a 7-element list

print $hash{"beer"};    # "good"
print $hash{"bananas"}; # "green"
print $hash{"wait"};    # "yellow";
print $hash{"eat"};     # undef, so prints "" and raises a warning

当然,这使得连接数个数组变得极其容易:

my @bones   = ("humerus", ("jaw", "skull"), "tibia");
my @fingers = ("thumb", "index", "middle", "ring", "little");
my @parts   = (@bones, @fingers, ("foot", "toes"), "eyeball", "knuckle");
print @parts;

更多的内容会在下文提及。

上下文

Perl最独特的特点是,代码是上下文敏感的。在Perl中的每一个表达式是通过标量上下文或者列表上下文来计算,这取决于是要生成标量或者是列表。大部分的Perl表达式和内置函数会在不同的上下文中,会表现出很不同的行为。

标量表达式例如$scalar= 在标量上下文中计算。在本例中,表达式是“Mendeleev”,返回值也是标量“Mendeleev”:

my $scalar = "Mendeleev";

数组或者哈希值赋值,例如@array = 或者%hash = 则是在列表上下文中演算。在列表上下文中计算的结果会以列表返回,然后赋值给数组或者哈希变量:

my @array = ("Alpha", "Beta", "Gamma", "Pie");
my %hash = ("Alpha" => "Beta", "Gamma" => "Pie");

目前位置,没什么特别的。

标量表达式在上下文中,则以一个元素的列表作为返回值:

my @array = "Mendeleev"; # same as 'my @array = ("Mendeleev");'

列表表达式在标量上下文中,则以列表的最后一个标量作为返回值:

my $scalar = ("Alpha", "Beta", "Gamma", "Pie"); # Value of $scalar is now "Pie"

数组表达式(数组和列表是不一样的,还记得吧?)在标量上下文中,返回数组的长度:

my @array = ("Alpha", "Beta", "Gamma", "Pie");
my $scalar = @array; # Value of $scalar is now 4

内置函数 print在列表上下文中计算所有的变量。事实上,print可以对变量中数组的没有限制大小要求,并且逐一打印数组中的元素,这意味着,可以直接打印数组:

my @array = ("Alpha", "Beta", "Goo");
my $scalar = "-X-";
print @array;              # "AlphaBetaGoo";
print $scalar, @array, 98; # "-X-AlphaBetaGoo98";

你可以强制任何的表达式在标量上下文中计算,只需使用 scalar内置函数。事实上,这就是为什么我们使用scalar去获取数组的长长度。

perl中没有限制在标量上下文的子过程中一定要返回标量,或者在列表上下文中一定要返回列表。就如上面的例子,Perl可以兼容不同的结果。

引用和嵌套数据结构

同列表无法将列表作为自身元素一样,数组和哈希表也无法将另一个数组和哈希表作为自身元素。它们只能包含一个标量,让我们试试看:

my @outer = ("Sun", "Mercury", "Venus", undef, "Mars");
my @inner = ("Earth", "Moon");

$outer[3] = @inner;

print $outer[3]; # "2"

$outer[3] 是一个标量,因此它需要一个标量值。当你尝试将一个类似 @inner 的数组值分配给它时,@inner 将被计算并存于标量内容分配标量 @inner 时亦是如此,值为数组 @inner 的长度,即为 2。

不过,一个标量变量可以存放一个对任何变量的引用,包括数组变量或是哈希表变量。使用 Perl 创建复杂的数据结构用的就是这种方式。

引用使用反斜杠创建。

my $colour    = "Indigo";
my $scalarRef = \$colour;

当你使用变量名时,你可以先使用一对大括号,然后将对变量的引用放进大括号内:

print $colour;         # "Indigo"
print $scalarRef;      # e.g. "SCALAR(0x182c180)"
print ${ $scalarRef }; # "Indigo"

图省事的话也可以不用大括号:

print $$scalarRef; # "Indigo"

如果是一个数组或哈希表变量的引用,你可以使用大括号或是比较流行的箭头操作符 -> 获取数据:

my @colours = ("Red", "Orange", "Yellow", "Green", "Blue");
my $arrayRef = \@colours;

print $colours[0];       # direct array access
print ${ $arrayRef }[0]; # use the reference to get to the array
print $arrayRef->[0];    # exactly the same thing

my %atomicWeights = ("Hydrogen" => 1.008, "Helium" => 4.003, "Manganese" => 54.94);
my $hashRef = \%atomicWeights;

print $atomicWeights{"Helium"}; # direct hash access
print ${ $hashRef }{"Helium"};  # use a reference to get to the hash
print $hashRef->{"Helium"};     # exactly the same thing - this is very common

声明数据结构

下面有4个例子,但通常最后一个更常用。

my %owner1 = (
	"name" => "Santa Claus",
	"DOB"  => "1882-12-25",
);

my $owner1Ref = \%owner1;

my %owner2 = (
	"name" => "Mickey Mouse",
	"DOB"  => "1928-11-18",
);

my $owner2Ref = \%owner2;

my @owners = ( $owner1Ref, $owner2Ref );

my $ownersRef = \@owners;

my %account = (
	"number" => "12345678",
	"opened" => "2000-01-01",
	"owners" => $ownersRef,
);

显然你不用这么费劲,可以简化为:

my %owner1 = (
	"name" => "Santa Claus",
	"DOB"  => "1882-12-25",
);

my %owner2 = (
	"name" => "Mickey Mouse",
	"DOB"  => "1928-11-18",
);

my @owners = ( \%owner1, \%owner2 );

my %account = (
	"number" => "12345678",
	"opened" => "2000-01-01",
	"owners" => \@owners,
);

也可以使用不同的符号声明匿名数组和哈希表。匿名数组使用方括号,匿名哈希表使用大括号。 这样,返回值就是一个对匿名数据结构的引用。细看一下, 返回的 %accountas 与上面相同:

# Braces denote an anonymous hash
my $owner1Ref = {
	"name" => "Santa Claus",
	"DOB"  => "1882-12-25",
};

my $owner2Ref = {
	"name" => "Mickey Mouse",
	"DOB"  => "1928-11-18",
};

# Square brackets denote an anonymous array
my $ownersRef = [ $owner1Ref, $owner2Ref ];

my %account = (
	"number" => "12345678",
	"opened" => "2000-01-01",
	"owners" => $ownersRef,
);

或者更短一些(这就是真正用来声明复杂数据结构的形式):

my %account = (
	"number" => "31415926",
	"opened" => "3000-01-01",
	"owners" => [
		{
			"name" => "Philip Fry",
			"DOB"  => "1974-08-06",
		},
		{
			"name" => "Hubert Farnsworth",
			"DOB"  => "2841-04-09",
		},
	],
);

获取信息的数据结构

现在,让我们假设,(如果有其他任何东西)范围已经下降了你仍然有个%accountkicking,可以打印信息,在每一种情况下扭转了相同的程序。现在这里有四个例子,其中最后一个是最有用的:

my $ownersRef = $account{"owners"};
my @owners    = @{ $ownersRef };
my $owner1Ref = $owners[0];
my %owner1    = %{ $owner1Ref };
my $owner2Ref = $owners[1];
my %owner2    = %{ $owner2Ref };
print "Account #", $account{"number"}, "\n";
print "Opened on ", $account{"opened"}, "\n";
print "Joint owners:\n";
print "\t", $owner1{"name"}, " (born ", $owner1{"DOB"}, ")\n";
print "\t", $owner2{"name"}, " (born ", $owner2{"DOB"}, ")\n";

或者,简称:

my @owners = @{ $account{"owners"} };
my %owner1 = %{ $owners[0] };
my %owner2 = %{ $owners[1] };
print "Account #", $account{"number"}, "\n";
print "Opened on ", $account{"opened"}, "\n";
print "Joint owners:\n";
print "\t", $owner1{"name"}, " (born ", $owner1{"DOB"}, ")\n";
print "\t", $owner2{"name"}, " (born ", $owner2{"DOB"}, ")\n";

或使用引用和>运算符:

my $ownersRef = $account{"owners"};
my $owner1Ref = $ownersRef->[0];
my $owner2Ref = $ownersRef->[1];
print "Account #", $account{"number"}, "\n";
print "Opened on ", $account{"opened"}, "\n";
print "Joint owners:\n";
print "\t", $owner1Ref->{"name"}, " (born ", $owner1Ref->{"DOB"}, ")\n";
print "\t", $owner2Ref->{"name"}, " (born ", $owner2Ref->{"DOB"}, ")\n";

如果我们完全跳过所有的中间值:

print "Account #", $account{"number"}, "\n";
print "Opened on ", $account{"opened"}, "\n";
print "Joint owners:\n";
print "\t", $account{"owners"}->[0]->{"name"}, " (born ", $account{"owners"}->[0]->{"DOB"}, ")\n";
print "\t", $account{"owners"}->[1]->{"name"}, " (born ", $account{"owners"}->[1]->{"DOB"}, ")\n";

搬起数组引用的石头砸自己的脚

下面的数组拥有5个元素:

my @array1 = (1, 2, 3, 4, 5);
print @array1; # "12345"

下面的数组拥有一个元素(它刚好是对一个匿名的,拥有5个元素的数组的引用):

my @array2 = [1, 2, 3, 4, 5];
print @array2; # e.g. "ARRAY(0x182c180)"

这个标量是对一个匿名的,拥有5个元素的数组的引用:

my $array3Ref = [1, 2, 3, 4, 5];
print $array3Ref;      # e.g. "ARRAY(0x22710c0)"
print @{ $array3Ref }; # "12345"
print @$array3Ref;     # "12345"

条件语句 

if...elsif...else...

在这里不要感到惊讶,除了elsif的拼写:

my $word = "antidisestablishmentarianism";
my $strlen = length $word;

if($strlen >= 15) {
	print "'", $word, "' is a very long word";
} elsif(10 <= $strlen && $strlen < 15) {
	print "'", $word, "' is a medium-length word";
} else {
	print "'", $word, "' is a a short word";
}

Perl提供一种更短的“ statement  if  condition "
语法,在短语句中这是非常值得推荐的:

print "'", $word, "' is actually enormous" if $strlen >= 20;

unless...else...

my $temperature = 20;

unless($temperature > 30) {
	print $temperature, " degrees Celsius is not very hot";
} else {
	print $temperature, " degrees Celsius is actually pretty hot";
}

unless语句块非常混乱,通常最好避免这种犹如瘟疫的东西。一个 ”unless [ ... else ] “语言块可以一点点地重构成”if [ ... else] "语句块,可以通过将条件取反 [或保持条件不变,交换语句块的位置]。幸运的是,不存在“elsunless”关键字。 

通过比较,由于易读性这被强烈推荐:

print "Oh no it's too cold" unless $temperature > 15;

三元运算符

三元运算符:?可以将简单的if语句嵌入到一个语句中。它的典型使用是单数/复数形式:

my $gain = 48;
print "You gained ", $gain, " ", ($gain == 1 ? "experience point" : "experience points"), "!";

旁白:单复数形式最全面阐明了这两种情况。不要自认为聪明做像下面这样的事情,因为任何人搜索的代码库以替换“tooth”或“teeth”,你将永远不会找到这行:

my $lost = 1;
print "You lost ", $lost, " t", ($lost == 1 ? "oo" : "ee"), "th!";

三元运算符可能被嵌套:

my $eggs = 5;
print "You have ", $eggs == 0 ? "no eggs" :
                   $eggs == 1 ? "an egg"  :
                   "some eggs";

if语句在标量的上下文中对它们的条件求值。例如,if(@array)返回真当且仅当@array有一个或多个元素。它并不关心这些元素是什么——他们可包含undef(未定义)或其它非真值。

循环

Perl中有多种方法实现循环:

常规的while循环:

my $i = 0;
while($i < scalar @array) {
	print $i, ": ", $array[$i];
	$i++;
}

Perl同样支持until关键字:

my $i = 0;
until($i >= scalar @array) {
	print $i, ": ", $array[$i];
	$i++;
}

do循环基本跟等价于上面的形式(当@array是空的时候,将会有一个警告信息):

my $i = 0;
do {
	print $i, ": ", $array[$i];
	$i++;
} while ($i < scalar @array);

my $i = 0;
do {
	print $i, ": ", $array[$i];
	$i++;
} until ($i >= scalar @array);

基本的C风格循环同样有效。注意,我们如何把一个my变量放在for语句中,这是通过在循环范围内定义变量$i实现的:

for(my $i = 0; $i < scalar @array; $i++) {
	print $i, ": ", $array[$i];
}
# $i has ceased to exist here, which is much tidier.

for循环被认为是古老的形式,并且尽可能避免使用。原生的列表迭代更美观。注意:和PHP不一样,for和foreach关键字是同义词。我们只用更容易阅读的方式:

foreach my $string ( @array ) {
	print $string;
}

如果你需要索引, range
operator
可以创建一个匿名整型列表:

foreach my $i ( 0 .. $#array ) {
	print $i, ": ", $array[$i];
}

你不能去迭代一个哈希变量。但是,你可以迭代它的键值。使用keys内置函数,获取哈希变量的所有键值数组。然后使用foreach方法,就像数组一样:

foreach my $key (keys %scientists) {
	print $key, ": ", $scientists{$key};
}

因为哈希变量没有顺序,键值可能以任何顺序被返回。使用sort内置函数对键值数组排序,按照字母表从小到大的方式:

foreach my $key (sort keys %scientists) {
	print $key, ": ", $scientists{$key};
}

如果使用默认的迭代,你只能在循环内部放置一条语句,你可以使用超级短的循环语法:

print $_ foreach @array;

循环控制

next和last可以用来控制循环的进度。在大部分的编程语言中,就如continue和break。我们可选择性的为任何循环提供标签。一般约定,标签使用大写字母。在标记了循环后,next和last就可以以标签为目的做跳转。以下的例子,查找100以下的素数:

CANDIDATE: for my $candidate ( 2 .. 100 ) {
	for my $divisor ( 2 .. <a target=_blank target="_blank" href="http://perldoc.perl.org/functions/sqrt.html" rel="nofollow" style="color: rgb(106, 57, 6); text-decoration: none;">sqrt</a> $candidate ) {
		next CANDIDATE if $candidate % $divisor == 0;
	}
	print $candidate." is prime\n";
}

数组相关函数

在数组中修改

我们会使用@stack来演示这些:

my @stack = ("Fred", "Eileen", "Denise", "Charlie");
print @stack; # "FredEileenDeniseCharlie"

pop  取出并返回数组的最后一个元素。这可以被认作是栈顶:

print pop @stack; # "Charlie"
print @stack;     # "FredEileenDenise"

push 追加额外的元素到数组末尾:

push @stack, "Bob", "Alice";
print @stack; # "FredEileenDeniseBobAlice"

shift 取出并返回数组的第一个元素:

print shift @stack; # "Fred"
print @stack;       # "EileenDeniseBobAlice"

unshift 插入一个新元素到数组开头:

unshift @stack, "Hank", "Grace";
print @stack; # "HankGraceEileenDeniseBobAlice"

pop,push,shift
and unshift是 splice(拼接)的特殊情况。splice删除并返回一个数组片段,用一个不同的数组片段代替它:

print splice(@stack, 1, 4, "<<<", ">>>"); # "GraceEileenDeniseBob"
print @stack;                             # "Hank<<<>>>Alice"

从已存在的数组创建新的数组

Perl提供下面的函数,从现有的数组创建新的数组。

join函数可以把多个字符串连接在成一个:

my @elements = ("Antimony", "Arsenic", "Aluminum", "Selenium");
print @elements;             # "AntimonyArsenicAluminumSelenium"
print "@elements";           # "Antimony Arsenic Aluminum Selenium"
print join(", ", @elements); # "Antimony, Arsenic, Aluminum, Selenium"

在列表上下文中,reverse函数返回反序的列表。在标量上下文中,reverse函数把列表的元素串接起来,把它当做一个词倒转返回。

print reverse("Hello", "World");        # "WorldHello"
print reverse("HelloWorld");            # "HelloWorld"
print scalar reverse("HelloWorld");     # "dlroWolleH"
print scalar reverse("Hello", "World"); # "dlroWolleH"

map函数接受数组作为输入,并对列表中每一个标量$_进行操作。然后创建一个新的数组。这里的操作通过花括号中以一个表达式的形式提供: 

my @capitals = ("Baton Rouge", "Indianapolis", "Columbus", "Montgomery", "Helena", "Denver", "Boise");

print join ", ", map { uc $_ } @capitals;
# "BATON ROUGE, INDIANAPOLIS, COLUMBUS, MONTGOMERY, HELENA, DENVER, BOISE"

grep函数接受一个数组作为输入,并返回一个经过滤的数组作为输出。语法跟map很相似。这一次,第二个参数是对输入数组中每一个标量$_进行计算。如果一个布尔值true被返回,标量将会放到输出数组,否则就会过滤掉:

print join ", ", grep { length $_ == 6 } @capitals;
# "Helena, Denver"

很明显,结果数组就是成功匹配的元素,这意味这你可以使用grep快速的判断一个数组是否包含某个元素:

print scalar grep { $_ eq "Columbus" } @capitals; # "1"

grep和map可以以 list
comprehensions
 的形式结合,一个异常强大的功能在其他语言是不具备的。

默认情况下,sort函数返回输入数组,并以单词顺序(字母顺序)排序:

my @elevations = (19, 1, 2, 100, 3, 98, 100, 1056);

print join ", ", sort @elevations;
# "1, 100, 100, 1056, 19, 2, 3, 98"

但是,与grep和map类似,你可以提供你自己的代码来进行排序。Sorting一般是通过一系列的两个元素间的比较来完成的。你的代码获取$a和$b输入,如果$a"小于"$b,则返回-1,如果”相等“则返回0,如果”大于“则返回1

cmp操作符就是对字符串完成这样的功能:

print join ", ", sort { $a cmp $b } @elevations;
# "1, 100, 100, 1056, 19, 2, 3, 98"

”飞船操作符“<=>则是对数字实现这样的功能:

print join ", ", sort { $a <=> $b } @elevations;
# "1, 2, 3, 19, 98, 100, 100, 1056"

$a和$b通常是标量,但是可能指向十分复杂的对象,以至于不能轻易做比较。如果你需要更多的空间来做比较运算,你可以创建一个独立的子过程并以它的名字作替代:

sub comparator {
	# lots of code...
	# return -1, 0 or 1
}

print join ", ", sort comparator @elevations;

你不能在grep或者map操作中这样做。

注意子过程和代码块从来没有显式的提供$a和$b.就像$_, $a 和$b,事实上,一对全局变量被广泛运用于每次比较当中。

内置函数

目前位置,你已经看到很多内置函数:print,sort,map,grep,keys,scalar等等。内置函数是Perl的强大武器之一。它们:

  • 量很大
  • 非常有用
  • 拥有大量的文档
  • 在不同的语法中会有很大差异,所以请查阅文档
  • 有时候接受正则表达式作为参数
  • 有时候接受整个代码块作为参数
  • 有时候在参数之间不需要逗号
  • 有时候需要随意个数的逗号来分隔参数,有时候又不需要
  • 有时候可以自动匹配变量,如果只提供了很少一部分变量
  • 通常不需要使用括号包围参数,除非在模糊的语义环境中

对于内置函数,最好的建议是知道它们的存在。抛开文档,直到以后有需要才去查阅。如果你的任务跟它的低层次和足够的通用,就如它之前完成了多次一样,那么机会就是它所拥有的。

用户定义子程序

子程序通过sub关键字定义。跟内置函数作对比,用户定义子程序通常接受同样的输入:一个包含标量的列表。列表可以只包含一个元素,或者是空的。一个标量被看做只包含一个元素的列表。带有N个元素的哈希变量被看做2N个元素的列表。

虽然括号是可选的,子程序通常通过括号被调用,甚至没有任何参数。这样可以清楚的看到一个子程序被调用。

当你在一个子程序里面,参数可以使用 内置 array变量@_来访问,例如:

sub hyphenate {

  # Extract the first argument from the array, ignore everything else
  my $word = shift @_;

  # An overly clever list comprehension
  $word = join "-", map { <a target=_blank target="_blank" href="http://perldoc.perl.org/functions/substr.html" rel="nofollow" style="color: rgb(106, 57, 6); text-decoration: none;">substr</a> $word, $_, 1 } (0 .. (<a target=_blank target="_blank" href="http://perldoc.perl.org/functions/length.html" rel="nofollow" style="color: rgb(106, 57, 6); text-decoration: none;">length</a> $word) - 1);
  return $word;
}

print hyphenate("exterminate"); # "e-x-t-e-r-m-i-n-a-t-e"

参数拆包

有多种方法对@_变量进行拆包,但是有一些比其他更优越。

例如子程序left_pad通过给定的填充字符来对输入字符作填充操作,长度为给定的长度。(x函数把同一个字符串复制多份并连接为一行)(注意,为了代码简短,这个子程序缺少一些基本的错误检查,例如确保填充字符只有一个字符,检查给定的长度要大于或者等于现在字符串的长度,对参数作所有必须的检查)

left_pad可以通过如下方式被调用:

print left_pad("hello", 10, "+"); # "+++++hello"
  1. 有些人不对参数进行拆包,而只是简单使用@_.这是错误并不被鼓励的。

    sub left_pad {
    	my $newString = ($_[2] x ($_[1] - length $_[0])) . $_[0];
    	return $newString;
    }
    
  2. 以下对@_的拆包方法,略低于强烈劝阻:
    sub left_pad {
    	my $oldString = $_[0];
    	my $width     = $_[1];
    	my $padChar   = $_[2];
    	my $newString = ($padChar x ($width - length $oldString)) . $oldString;
    	return $newString;
    }
    
  3. 通过使用shift删除数据来对@_拆包,这种方法对4个参数的时候推荐使用:
    sub left_pad {
    	my $oldString = shift @_;
    	my $width     = shift @_;
    	my $padChar   = shift @_;
    	my $newString = ($padChar x ($width - length $oldString)) . $oldString;
    	return $newString;
    }
    

    如果没有数组提供给shift函数,那么它隐式的对@_进行操作。这种方法很常见:

    sub left_pad {
    	my $oldString = shift;
    	my $width     = shift;
    	my $padChar   = shift;
    	my $newString = ($padChar x ($width - length $oldString)) . $oldString;
    	return $newString;
    }
    

    超过4个参数的情况,就很难跟踪什么在哪里被赋予了值。

  4. 你可以同时使用多个标量赋值,对@_整体进行拆包。同样,这对于4个参数的情况才可行:
    sub left_pad {
    	my ($oldString, $width, $padChar) = @_;
    	my $newString = ($padChar x ($width - length $oldString)) . $oldString;
    	return $newString;
    }
    
  5. 对于带有很多个参数的子程序,或者某些参数是可选的,或者不能被用于和其他组合,最好的方法是需要用户在调用函数的时候,提供一个参数的哈希变量。对于这种方法,我们的子程序被调用的时候会有点不一样:
    print left_pad("oldString" => "pod", "width" => 10, "padChar" => "+");
    

    子程序如下:

    sub left_pad {
    	my %args = @_;
    	my $newString = ($args{"padChar"} x ($args{"width"} - length $args{"oldString"})) . $args{"oldString"};
    	return $newString;
    }
    

返回值

就如其他Perl表达式,调用子程序也会在不同上下文中有不同的行为。你可以使用wantarray函数(也可以叫做wantlist)来检测子程序处于什么上下文当中,并返回一个合适的结果到上下文:

sub contextualSubroutine {
	# Caller wants a list. Return a list
	return ("Everest", "K2", "Etna") if wantarray;

	# Caller wants a scalar. Return a scalar
	return 3;
}

my @array = contextualSubroutine();
print @array; # "EverestK2Etna"

my $scalar = contextualSubroutine();
print $scalar; # "3"

系统调用

请原谅,如果你已经直到下面的跟Perl无关的事实。在windows或者linux(我假设,在大部分的其他系统)一个进程每次完成的时候,它就会总结16位的status word。高8位由返回代码(0到255)组成,0一般代表没有正确完成,其他值代表不同的错误程度。剩下的8为很少被检查 —— 它们”反应的是错误的模式,就如终止信号和内核信息“。

你可以在一个Perl脚本中,按照你的选择(从0到255)使用exit退出。

Perl提供不止一种信号调用方法 - spwan一个子进程,暂停当前的脚本,直到子进程完成为止,然后恢复解释当前脚本。不管使用哪个方法,之后你会发现,内置scalar变量$?已经被赋值为子进程结束时返回的状态。你可以使用$?
>> 8 语句获取16位代码的高8位返回代码.

system函数可以用于调用其他程序,并附带参数。system的返回值和$?返回的是同样的值:

my $rc = system "perl", "anotherscript.pl", "foo", "bar", "baz";
$rc >>= 8;
print $rc; # "37"

另外,你可以使用反引号``来运行命令,并从命令获取标准输出。在标量上下文中,整个输出是以一个字符串的形式被返回。在列表上下文,整个输出则是以字符串的数组被返回,每一个元素代表一行输出。

my $text = `perl anotherscript.pl foo bar baz`;
print $text; # "foobarbaz"

以下是包含其他.pl脚本的时候将会看到的情况,例如:

use strict;
use warnings;

print @ARGV;
exit 37;

文件和文件句柄

标量变量可能包含一个文件句柄,而不是数字/字符串/引用或者是undef。文件句柄实际上是一个特定文件的特定地址的引用。

使用open函数把标量变量转换为文件句柄。open必须提供一个模式。模式 < 表示我们是希望打开这个文件并从中读取数据:

my $f = "text.txt";
my $result = open my $fh, "<", $f;

if(!$result) { <a target=_blank target="_blank" href="http://perldoc.perl.org/functions/die.html" rel="nofollow" style="color: rgb(106, 57, 6); text-decoration: none;">die</a> "Couldn't open '".$f."' for reading because: ".$!;
}

如果成功,open返回一个true的值。否则,返回false和通过内置变量$!返回一条错误消息。就如上面看到的,你必须检查open操作是否成功。这种检查十分乏味,一种常见的习惯写法是:

open(my $fh, "<", $f) || die "Couldn't open '".$f."' for reading because: ".$!;

请注意,需要用括号包含open调用的参数。

从一个文件句柄中读取一行文本,使用readline内置函数。readline返回一整行的文本,并在最后附加一个换行符(除非是文件的最后一行),或者当你读取到文件末尾的时候,则返回undef。

while(1) {
	my $line = readline $fh;
	last unless defined $line;
	# process the line...
}

使用 chomp去除换行符:

chomp $line;

请注意,chomp会直接操作$line变量,所以$line = chomp $line并非你想要的结果。

你可以使用eof来检测是否已经到达文件末尾。

while(!eof $fh) {
	my $line = readline $fh;
	# process $line...
}

但是要知道,只需要使用while(my $line = readline $fh),因为if $line返回的是“0”,循环会早早的结束。如果你希望这样写,Perl提供<>操作符,以安全的形式包含readline。这很常见,并且十分安全:

while(my $line = <$fh>) {
	# process $line...
}

甚至是:

while(<$fh>) {
	# process $_...
}

如果需要写一个文件的话,那么打开文件的时候要用不同的模式。模式 > 表示我们希望打开文件并对它进行写操作(> 可能会跟已经存在的文件,并有文件内容的情况发生冲突)然后,只需把文件句柄提供给print函数,就像零参数的形式。

open(my $fh2, ">", $f) || die "Couldn't open '".$f."' for writing because: ".$!;
print $fh2 "The eagles have left the nest";

请注意,$fh2和另外一个参数之间没有逗号。

文件句柄会在它们没有引用的时候自动关闭,但是除此之外:

<a target=_blank target="_blank" href="http://perldoc.perl.org/functions/close.html" rel="nofollow" style="color: rgb(106, 57, 6); text-decoration: none;">close</a> $fh2;
close $fh;

3个文件句柄会议全局常量的形式存在:STDIN, STDOUT和STDERR。它们会在脚本开始的时候,自动open。读取用户的输入:

my $line = <STDIN>;

等待用户的输入回车:

<STDIN>;

调用<>而没有文件句柄的时候,会从STDIN读取数据,或者当Perl脚本被调用的时候,参数指定的其他文件中读取数据。

print会默认输出到STDOUT,如果没有指定具体的文件句柄。

文件测试

函数-e是一个内置函数,测试文件是否存在。

print "what" unless -e "/usr/bin/perl";

函数-d则是一个内置函数,测试指定的文件是否是一个目录。

函数-f测试给定的文件是否是一个文本文件。

这是a large class of functions中的三个函数,以-X 的形式,X是小写或者大写字母。这些函数被叫做文件测试。请注意,前缀的减号。在google查询中,减号表示排除这种样式的结果。这样导致文件测试很难在Google中搜索相关资料。所以可以用“perl
file test”作为关键字替代。

正则表达式

正则表达式在很多语言中都会出现,并且是一个工具。Perl核心的正则表达式语法跟其他语言基本一样,但是Perl的完整正则表达式的能力则十分复杂和难以理解。我能给你的最好建议就是尽量避免使用复杂的形式。

通过=~ m//实现匹配操作。在标量上下文中,=~ m//如果成功则返回true,失败则返回false。

my $string = "Hello world";
if($string =~ m/(\w+)\s+(\w+)/) {
	print "success";
}

括号里面执行子匹配。当完成了一个成功的匹配操作后,子匹配则通过$1, $2, $3, ....返回值。

print $1; # "Hello"
print $2; # "world"

在列表上下文中,=~ m//则以列表的形式返回$1, $2...

my $string = "colourless green ideas sleep furiously";
my @matches = $string =~ m/(\w+)\s+((\w+)\s+(\w+))\s+(\w+)\s+(\w+)/;

print join ", ", map { "'".$_."'" } @matches;
# prints "'colourless', 'green ideas', 'green', 'ideas', 'sleep', 'furiously'"

替代操作通过 = ~ s///完成。

my $string = "Good morning world";
$string =~ s/world/Vietnam/;
print $string; # "Good morning Vietnam"

注意$string的内容是如何被修改的。你需要传递一个标量参数给 =~s///操作符的左边。如果你传递了一个字符串,你得到的就是一个错误。

/g标签标识"组匹配"。

在标量上下文中,每一个 =~ m//g 调用在完成一个匹配后继续查找下一个匹配,成功则返回true,失败返回false。你可以访问$1和其他后续变量,和平常一样。例如:

my $string = "a tonne of feathers or a tonne of bricks";
while($string =~ m/(\w+)/g) {
  print "'".$1."'\n";
}

在列表上下文中, =~m//g一次过返回所有的匹配。

my @matches = $string =~ m/(\w+)/g;
print join ", ", map { "'".$_."'" } @matches;

=~s///g调用实现全局的搜索/替代操作,并返回成功匹配的个数。下面,我们使用"r"替代所有的元音。

# Try once without /g.
$string =~ s/[aeiou]/r/;
print $string; # "r tonne of feathers or a tonne of bricks"

# Once more.
$string =~ s/[aeiou]/r/;
print $string; # "r trnne of feathers or a tonne of bricks"

# And do all the rest using /g
$string =~ s/[aeiou]/r/g;
print $string, "\n"; # "r trnnr rf frrthrrs rr r trnnr rf brrcks"

 /i 标签表示匹配/替代对大小写敏感。

 /x 标签允许你的正则表达式包含空格(例如,换行符)和注释。

"Hello world" =~ m/
  (\w+) # one or more word characters
  [ ]   # single literal space, stored inside a character class
  world # literal "world"
/x;

# returns true

模块和包

模块和包在Perl是不一样的。

模块

模块是一个.pm文件,可以在其他Perl文件(脚本或者模块)中引用。模块是一个文本文件,和perl脚本.pl的语法一模一样。模块的例子可以参考C:\foo\bar\baz\Demo\StringUtils.pm或者/foo/bar/baz/Demo/StringUtils.pm,代码如下:

use strict;
use warnings;

sub zombify {
	my $word = shift @_;
	$word =~ s/[aeiou]/r/g;
	return $word;
}

return 1;

因为当一个模块被载入时,模块是从头到尾的执行,你需要在末尾返回一个true值,表示这个模块已经载入成功。

为了让Perl解释器可以找到它们,在调用perl之前,包含perl模块的目录必须在环境变量PERL5LIB中列明。列出包含模块的根目录,而不要列出模块所在目录或者模块本身:

set PERL5LIB=C:\foo\bar\baz;%PERL5LIB%

或者

export PERL5LIB=/foo/bar/baz:$PERL5LIB

一旦Perl模块被创建,perl就会知道哪里才能找到它们,你可以在perl脚本中使用 require函数搜索并执行它。例如,调用require Demo::StringUtils会触发Perl解释器在PERL5LIB的目录中逐一搜索,查找名为Demo/StringUtils.pm的文件。模块被执行完后,模块中定义的子程序现在对于主脚本来说就是可调用的了。我们的例子中可能叫做main.pl,并且代码如下:

use strict;
use warnings;

require Demo::StringUtils;

print zombify("i want brains"); # "r wrnt brrrns"

注意,使用双冒号隔开目录和模块。

现在出现一个问题:如果main.pl包含很多require语句,并且每个模块包含很多require,那么我们就很难去追踪原始的zombify定义到底是在哪里。解决方法就是使用包。

包是一个命名空间,在里面可以定义子程序。任何你定义的子程序都隐式定义在当前包中。在开始执行时候,你所在的包是main包,但是你可以使用package函数切换包。

use strict;
use warnings;

sub subroutine {
	print "universe";
}

package Food::Potatoes;

# no collision:
sub subroutine {
	print "kingedward";
}

注意,使用双引号作为命名空间的分隔符。

当你调用子程序的时候,那你隐式的调用了当前包下的对应子程序。另外,你可以显示的指明一个包。看看上面的脚本继续执行会有什么结果:

subroutine();                 # "kingedward"
main::subroutine();           # "universe"
Food::Potatoes::subroutine(); # "kingedward"

上面问题的逻辑解决方法是C:\foo\bar\baz\Demo\StringUtils.pm或者/foo/bar/baz/Demo/StringUtils.pm:

use strict;
use warnings;package Demo::StringUtils;sub zombify {
	my $word = shift @_;
	$word =~ s/[aeiou]/r/g;
	return $word;
}

return 1;

并且修改main.pl:

use strict;
use warnings;

require Demo::StringUtils;

printDemo::StringUtils::zombify("i want brains"); # "r wrnt brrrns"

现在,细心的阅读下面的内容。

包和模块是中完全独立和Perl中不同的特性。它们都是使用双冒号作为分隔符。在一个脚本或者模块中可以多次切换包,并且可以在多个位置和多个文件中使用同样的包定义。调用require Foo::Bar并不会查找和载入一个包含Foo::Bar包定义的文件,也不需要载入Foo::Bar命名空间内的子程序。调用require Foo::Bar只会载入一个名为Foo/Bar.pm的文件,并且不一定要在文件内部定义任何的包,实际上,可能在里面是定义了Baz::Qux和其他不相关的包。

同样,一个子程序调用Baz::Qux::processThis()并不一定是定义在名为Baz/Qux.pm的文件里面。它可以在任何地方被定义。

区分这俩个概念,是perl中最愚蠢的特性,并且把它们看作不同的不变的概念会导致混乱,和令人发狂的代码。幸好,大部分perl程序员遵循以下两条规则:

  1. perl脚本(.pl)必须不能包含包的声明
  2. perl模块(.pm)必须只能声明一个包,并且对应模块的名字和位置。例如 moduleDemo/StringUtils.pm必须以Demo::StringUtils包名开始。

因为这两条规则,在实际操作中,你会发现可信的第三方提供的包和模块,可以被认为可以互换的。但是,有一点很重要,你不能把这当成理所当然的,因为你某天可能会碰到一个疯子写的代码。

面向对象的Perl

perl并非是一个非常好的面向对象语言。perl的面向对象能力事实上是被嫁接的,以下将说明:

  • 对象是一个引用(例如标量变量),为了知道它是属于哪个类。为了声明引用是属于哪个类,可以使用bless函数. 为了找出引用的指向是属于哪个类,可以使用ref.
  • 方法是一个子程序,并以对象(或者,对于类方法,就是一个包名)作为它的第一个参数。对象方法通过使用$obj->method()调用;类方法通过使用Package::Name->method()调用。
  • 类就是一个包含方法的包。

通过一个简单的例子,就很容易明白。Animal.pm模块包含类Animal:

use strict;
use warnings;

package Animal;

sub eat {
	# First argument is always the object to act upon.
	my $self = shift @_;

	foreach my $food ( @_ ) {
		if($self->can_eat($food)) {
			print "Eating ", $food;
		} else {
			print "Can't eat ", $food;
		}
	}
}

# For the sake of argument, assume an Animal can eat anything.
sub can_eat {
	return 1;
}

return 1;

我们可以这样使用这个类:

require Animal;

my $animal = {
	"legs"   => 4,
	"colour" => "brown",
};                       # $animal is an ordinary hash reference
print ref $animal;       # "HASH"
bless $animal, "Animal"; # now it is an object of class "Animal"
print ref $animal;       # "Animal"

注意:几乎任何引用都可以指向任何类。这完全由你决定:(1)引用作为类的实例来使用,还有(2)类必须存在且已经被载入。

你仍能使用原来的哈希变量

print "Animal has ", $animal->{"legs"}, " leg(s)";

但是,你还可以通过->操作符调用对象的方法:

$animal->eat("insects", "curry", "eucalyptus");

这个调用等价于Animal::eat($animal, "insects", "curry", "eucalyptus").

构造函数

构造函数是一个类的方法,返回一个新的对象。如果你需要创建一个,只需要定义即可。你可以使用任何的名称。对于类方法,第一个参数不是对象而是类的名字。在这个例子中,就是“Animal”:

use strict;
use warnings;

package Animal;

sub new {
	my $class = shift @_;
	return bless { "legs" => 4, "colour" => "brown" }, $class;
}

# ...etc.

通过如下方式使用:

my $animal = Animal->new();

继承

要创建一个继承自父类的类,则需要使用parent。让我们假设我们的子类叫AnimalwithKoala,在Koala.pm文件里面:

use strict;
use warnings;

package Koala;

# Inherit from Animal
use parent ("Animal");

# Override one method
sub can_eat {
	my $self = shift @_; # Not used. You could just put "shift @_;" here
	my $food = shift @_;
	return $food eq "eucalyptus";
}

return 1;

样例代码:

use strict;
use warnings;

require Koala;

my $koala = Koala->new();

$koala->eat("insects", "curry", "eucalyptus"); # eat only the eucalyptus

最后的方法尝试调用Koala::eat($koala, "insects", "curry", "eucalyptus"), 但是有一个子程序eat()并没有在Koala包中定义。然而,因为Koala有一个父类Animal,Perl解释器尝试调用Animal::eat($koala, "insects", "curry", "eucalyptus")来代替,而这个可以正常工作。注意Animal类是如何自动通过Koala.pm载入的。

因为使用了parent接受了一系列的父类名称,perl支持多继承,这样有利有弊。

BEGIN语句块

BEGIN语句块在perl完成解析该块的时候被执行,甚至在文件其他代码被解析之前。在执行的时候会被忽略:

use strict;
use warnings;

print "This gets printed second";

BEGIN {
	print "This gets printed first";
}

print "This gets printed third";

BEGIN块一般首先被执行。你可以创建多个BEGIN块(但是最好不要),它们被从头到尾的执行。BEGIN块通常被第一个执行即使它是在脚本的中间(但不要这样做)或者在最后(也不要这样做)。不要把它混在正常代码里面。吧BEGIN块放在开头!

BEGIN块会在该块被解析的时候就执行。当完成后,解析会回到BEGIN块末尾继续往下解析。只有当整个脚本或者模块被解析后,BEGIN块之外的代码就会被执行。

use strict;
use warnings;

print "This 'print' statement gets parsed successfully but never executed";

BEGIN {
	print "This gets printed first";
}

print "This, also, is parsed successfully but never executed";

...because e4h8v3oitv8h4o8gch3o84c3 there is a huge parsing error down here.

因为它们在编译的时候就会被执行,BEGIN块放在条件块里面也会首先被执行,即使条件判断是false,不管条件判断是否被执行,实际上永远不会去判断:

if(0) {
	BEGIN {
		print "This will definitely get printed";
	}
	print "Even though this won't";
}

不要把BEGIN块放在条件判断语句内。如果你要在某些条件下才执行,你需要把条件判断放在BEGIN块里面:

BEGIN {
	if($condition) {
		# etc.
	}
}

use

现在,你已经知道迟钝的行为和包的语义,模块,类方法和BEGIN块。现在可以解释经常看到的use函数。

以下3个语句:

use Caterpillar ("crawl", "pupate");
use Caterpillar ();
use Caterpillar;

跟下面是等价的:

BEGIN {
	require Caterpillar;
	Caterpillar->import("crawl", "pupate");
}
BEGIN {
	require Caterpillar;
}
BEGIN {
	require Caterpillar;
	Caterpillar->import();
}
  • 三个例子并不是顺序有误。这只是因为Perl沉默的
  • use调用实际是BEGIN块的伪装。使用use语句同样要放在文件的头部,不要放在条件判断语句里面。
  • import()并不是一个内置perl函数。它是一个用户定义的类方法。这个由程序员在Caterpillar包中定义或者继承import(),并且这个方法理论上接受任何淡出,并使用这些参数做任何操作。参阅Caterpillar.pm的文档看看实际会发生什么。
  • 注意require Caterpillar是如何载入一个名为Caterpillar.pm的模块,而Caterpillar->import()调用Caterpillar包中定义的import()子程序。希望模块和包是一致的。

Exporter

最常见的定义import()方法的途径是继承Exporter模块。Exporter是一个核心模块,和在Perl编程语言中的一个de facto 核心功能。在Exporter的对import实现中,你传递该它的参数列表被翻译为一个子程序名字的列表。当以恶搞子程序被imoprt进去,它在当前包和原来的包都是可用的。

通过例子,就很容易理解这个概念。以下是Caterpillar.pm的代码:

use strict;
use warnings;

package Caterpillar;

# Inherit from Exporter
use parent ("Exporter");

sub crawl  { print "inch inch";   }
sub eat    { print "chomp chomp"; }
sub pupate { print "bloop bloop"; }

our @EXPORT_OK = ("crawl", "eat");

return 1;

包变量@EXPORT_OK必须包含一个子程序名字的列表.

另外的代码使用import()通过子程序的名字载入它们,一般使用use语句。

use strict;
use warnings;
use Caterpillar ("crawl");

crawl(); # "inch inch"

在这个例子中,当前包是main, 所以crawl()调用实际是调用main::crawl(), 并映射到(因为它已经被import进来了)Caterpillar::crawl().

注意: 不管@EXPORT_OK里面的内容是什么, 每一个方法都可以被叫做"longhand":

use strict;
use warnings;
use Caterpillar (); # no subroutines named, no import() call made

# and yet...
Caterpillar::crawl();  # "inch inch"
Caterpillar::eat();    # "chomp chomp"
Caterpillar::pupate(); # "bloop bloop"

perl没有私有方法。通常,私有方法以一条或者两条下划线做前缀来命名。

@EXPORT

Exporter模块同样定义了一个包变量,叫做@EXPORT,可以使用子程序名字列表来填充。

use strict;
use warnings;

package Caterpillar;

# Inherit from Exporter
use parent ("Exporter");

sub crawl  { print "inch inch";   }
sub eat    { print "chomp chomp"; }
sub pupate { print "bloop bloop"; }

our @EXPORT = ("crawl", "eat", "pupate");

return 1;

当调用没有变量的import()的时候,@EXPORT里面的子程序名就会被export:

use strict;
use warnings;
use Caterpillar; # calls import() with no arguments

crawl();  # "inch inch"
eat();    # "chomp chomp"
pupate(); # "bloop bloop"

但是注意,我们回到这个场景,我们很难区分crawl()实际在哪里被定义的。这有双重意义:

  1. 当使用Exporter创建一个模块,不要使用@EXPORT来导出子程序。通常,让用户调用子程序"longhand"或者import()显式的导入进来(使用Caterpillar("crawl"),这是一个很好的线索去搜索Caterpillar.pm查看crawl()的定义)
  2. 当通过Exporter使用模块,通常显式命名需要import的子程序。当你不希望import任何子程序,而是希望参考他们的longhand,你必须显式的提供一个空列表:use Caterpillar ().

其他要注意的

  • 核心模块Data::Dumper 可以用于随意输出一个标量到屏幕。这实际是一个调试工具。
  • 这里有另外一种语法,qw{},定义数组。通常在use语句中看到:
    use Account qw{create open close suspend delete};
  • 还有很多其他quote-like操作符
  • 在=~ m//and=~ s///操作中,你可以使用花括号代替斜杠作为正则表达式的分隔符。当正则表达式包含大量的斜杠,需要转义反斜杠的情况,这种语法十分有用。例如=~ m{///}匹配3个斜杠,还有=~ s{^https?://}{}去掉URL里面的协议名称。
  • Perl其实有常量。但是现在不被推荐,但也并不是所有场合都是。常量实际是没有括号的子程序。
  • 有时候,人们忽略哈希键值的引号,写成$hash{key}代替$hash{"key"}。这样会产生歧义,因为没有括号引用的key值会被当作调用子程序key()
  • 如果看到一个包裹在双尖括号,例如<<EOF, 没有格式化的语句块,那么google的神奇单词"here-doc"就派上用场。
  • 注意!很多内置函数可以被调用,并不需指定参数,使它们以$_来代替。希望这个可以让你更好的理解这个格式:
    print foreach @array;

    还有

    foreach ( @array ) {
    	next unless defined;
    }

    我并不喜欢这个格式,因为在重构的时候会有问题。

时间: 2024-10-22 07:36:31

Perl的相关文章

断点续传-请问perl是否可以改写nginx服务器得到的POST请求地址,并且不能丢失post的数据

问题描述 请问perl是否可以改写nginx服务器得到的POST请求地址,并且不能丢失post的数据 场景:后端JAVA应用作了一个断点续传的功能,测试OK,由于一些环境限制原因,前端必须用nginx转发.坑爹的是nginx的rewrite功能会把post请求变为get请求,如果使用 proxy pass则会出现如果用户上传中断,nginx不会把已经上传的东西扔给后端应用 现在考虑方向是改写post请求的链接(改成IP加端口)但是不更改别的东西,比如post的数据,方法等等,或者干脆收到此种请求

perl正则表达式匹配问题

问题描述 perl正则表达式匹配问题 10C 本人小白看不懂前辈写的perl正则表达式 m/=$/ 和 m/[=]/区别,这个要匹配的是哪两种情况 解决方案 后面的表达式是不是不完整 解决方案二: 正则表达式匹配问题perl正则表达式匹配正则表达式之匹配顺序问题 解决方案三: 本人小白看不懂前辈写的perl正则表达式 m/=$/ 和 m/[=]/区别,这个要匹配的是哪两种情况 前一个是匹配一个=号结尾的行,例如 abcdkjalsjfajsflajsfl=而后一个是匹配文本行中 包含 = 号的,

使用 cpanm 安装 Perl 模块

 cpanm 安装 Perl 模块 目录: 本文简介 1 概述 2 安装 cpanm 3 使用 cpanm 本文简介 概要:使用 cpanm 安装 Perl 模块 版本: Debian 5 (Lenny), cpanminus 1.0015 日期:2010-11-01 永久链接:http://sleepycat.org/tech/perl/cpanm 1 概述 cpanm 是所用过的安装 Perl 模块的最方便的方法. 关于 cpanm: http://search.cpan.org/~miya

compile httpd 2.4.9, perl, php in CentOS 6.x x64

httpd 2.4.9 在CentOS 6.x x64上的安装过程. 一. apr安装 http://apr.apache.org/download.cgi # wget http://mirror.bit.edu.cn/apache//apr/apr-1.5.1.tar.bz2 # tar -jxvf apr-1.5.1.tar.bz2 # cd apr-1.5.1 # ./configure --prefix=/opt/apr1.5.1 # make && make test # ma

用perl来实现三重des的算法,不要伪代码

问题描述 用perl来实现三重des的算法,不要伪代码 三重des的伪代码理解,但是在用perl实现的时候,出现差错,希望有程序 解决方案 ****楼上正解******** 解决方案二: http://blog.csdn.net/gary162/article/details/50629749 解决方案三: 没实现过,只能推荐了:http://blog.sina.com.cn/s/blog_475cb6780100rft7.html

求一个递归修改文件夹内全部子文件和文件夹名的程序(batch或者perl)

问题描述 求一个递归修改文件夹内全部子文件和文件夹名的程序(batch或者perl) 需求是这样的: 递归修改文件夹中所有名字带"aaa"字符串的 文件夹名或者文件名改成 bbb 例如: 01_aaa |_01_aaa_01 |_nbdaaa_01.txt |_nbcaaa_02.txt |_02_aaa_01 改成 01_bbb |_01_bbb_01 |_nbdbbb_01.txt |_nbcbbb_02.txt |_02_bbb_01 解决方案 http://blog.163.c

使用Perl连接Mysql数据库

网站后台数据库转为Mysql,现在使用Perl连接数据库就方便多了. 通过DBI,Perl可以很容易的连接到数据库: #!/bin/perl use DBI; # Connect to target DBmy $dbh = DBI->connect("DBI:mysql:database=eygle;host=localhost","username","password", {'RaiseError' => 1}); # Inser

用Perl编写读取POP3邮箱的应用程序

本文将讨论开发人员利用Perl特定的能力编写POP3电子邮件应用程序. 像微软的Outlook和Mozilla的雷鸟(Thunderbird)这样的电子邮件客户端程序能够让收发电子邮件变得极其简单--绝大多数时候,你需要做的只不过是点击一下工具条上的按钮,软件会为你完成与电子邮件服务器进行通信.验证密码.收取电子邮件等复杂的工作. 但是在这种具有欺骗性的简单表象背后却牵涉大量的软件编程工作.而且,如果你是一个软件开发人员,你可能有一天会发现如果能够把这样的程序放到自己的应用程序会有多好啊. 不用

Perl的Ajax实现与中文问题

ajax|perl|问题|中文 Ajax, 最近非常红火的技术.有很多现成的开发工具包.开始的时候我试用了CPAINT,支持php/asp.还不错.后来开始用perl的Ajax实现: CGI::Ajax.对比之下,发现非常好用.主要的特点是程序自动生成javascript调用的代码. 这样的话,我们可以专注于程序逻辑的开发,而不用去理睬繁复的Ajax javascript调用.这是CGI::Ajax高明的地方.CGI::Ajax安装 perl -MCPAN -e "install CGI::Aj

基于Sendmail和Perl的邮件附件过滤系统(4)

perl 1; 上面的过滤规则表示删除带上面所列出的扩展名的邮件且通知发送者邮件被删除,接收其它所有的邮件.我也更改/usr/local/bin/mimedefang.pl文件以致不要保留邮件附件在/var/spool/MIMEDefang目录中,该文件有详细的自我解释,请编辑该文件去掉保留一份被删除邮件附件在硬盘上的部分. f. 启动系统并测试 简单地拷贝example目录下的为redhat而写的redhat-sendmail-init-script到 /etc/rc.d/init.d/sen