加入收藏 | 设为首页 | 会员中心 | 我要投稿 李大同 (https://www.lidatong.com.cn/)- 科技、建站、经验、云计算、5G、大数据,站长网!
当前位置: 首页 > 大数据 > 正文

两个半小时的Perl飞升之旅

发布时间:2020-12-16 00:04:02 所属栏目:大数据 来源:网络整理
导读:Perl 语言的设计哲学是:"每个问题都有许多解决方式"(TMTOWTDI)( 与之相反,Python的设计哲学是:每个问题应该只有一种,而且只有一种明确的,最好的解决方式)。 AD: 2013云计算架构师峰会课程资料下载 Perl 语言是一门高级解释型动态语言,它的许多数据
Perl 语言的设计哲学是:"每个问题都有许多解决方式"(TMTOWTDI)( 与之相反,Python的设计哲学是:每个问题应该只有一种,而且只有一种明确的,最好的解决方式)。

AD: 2013云计算架构师峰会课程资料下载


    Perl语言是一门高级解释型动态语言,它的许多数据类型是运行时才确定的,并且经常和PHPPython相提并论。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))

    ·字符串

    引用任何其他变量。

       
       
    1. my?$undefundef?=?undef; ?
    2. print?$undef;?#?prints?the?empty?string?""?and?raises?a?warning ?
    3. ?
    4. #?implicit?undef: ?
    5. my?$undef2; ?
    6. print?$undef2;?#?prints?""?and?raises?exactly?the?same?warning?
       
       
    1. my?$num?=?4040.5; ?
    2. print?$num;?#?"4040.5"?
       
       
    1. my?$string?=?"world"; ?
    2. print?$string;?#?"world"?

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

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

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

    布尔值(Booleans)

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

    ·undef

    ·数字 0

    ·字符串 ""

    ·字符串 "0"

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

    弱类型(Weak typing)

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

       
       
    1. my?$str1?=?"4G"; ?
    2. my?$str2?=?"4H"; ?
    3. ?
    4. print?$str1?.??$str2;?#?"4G4H" ?
    5. print?$str1?+??$str2;?#?"8"?with?two?warnings ?
    6. print?$str1?eq?$str2;?#?""?(empty?string,?i.e.?false) ?
    7. print?$str1?==?$str2;?#?"1"?with?two?warnings ?
    8. ?
    9. #?The?classic?error ?
    10. 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 中被称为关联数组。

       
       
    1. my?%scientists?=?( ?
    2. ????"Newton"???=>?"Isaac",?
    3. ????"Einstein"?=>?"Albert",?
    4. ????"Darwin"???=>?"Charles",?
    5. ); ?

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

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

       
       
    1. print?$scientists{"Newton"};???#?"Isaac" ?
    2. print?$scientists{"Einstein"};?#?"Albert" ?
    3. print?$scientists{"Darwin"};???#?"Charles" ?
    4. print?$scientists{"Dyson"};????#?returns?undef,?prints?""?and?raises?a?warning ?

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

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

       
       
    1. my?@scientists?=?%scientists;?

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

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

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

       
       
    1. my?$data?=?"orange"; ?
    2. my?@data?=?("purple"); ?
    3. my?%data?=?(?"0"?=>?"blue"); ?
    4. ?
    5. print?$data;??????#?"orange" ?
    6. print?$data[0];???#?"purple" ?
    7. print?$data["0"];?#?"purple" ?
    8. print?$data{0};???#?"blue" ?
    9. print?$data{"0"};?#?"blue"??

    列表(list)

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

       
       
    1. ( ?
    2. ????"print",?
    3. ????"these",?
    4. ????"strings",?
    5. ????"out",?
    6. ????"for",?
    7. ????"me",?
    8. ) ?
    9. ?
    10. ( ?
    11. ????"Newton"???=>?"Isaac",?
    12. ????"Einstein"?=>?"Albert",?
    13. ????"Darwin"???=>?"Charles",?
    14. ) ?

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

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

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

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

       
       
    1. ()?

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

       
       
    1. my?@array?=?( ?
    2. ????"apples",?
    3. ????"bananas",?
    4. ????( ?
    5. ????????"inner",?
    6. ????????"list",?
    7. ????????"several",?
    8. ????????"entries",?
    9. ????),?
    10. ????"cherries",?
    11. ); ?

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

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

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

       
       
    1. my?%hash?=?( ?
    2. ????"beer"?=>?"good",?
    3. ????"bananas"?=>?( ?
    4. ????????"green"??=>?"wait",?
    5. ????????"yellow"?=>?"eat",?
    6. ????),?
    7. ); ?
    8. ?
    9. #?The?above?raises?a?warning?because?the?hash?was?declared?using?a?7-element?list ?
    10. ?
    11. print?$hash{"beer"};????#?"good" ?
    12. print?$hash{"bananas"};?#?"green" ?
    13. print?$hash{"wait"};????#?"yellow"; ?
    14. print?$hash{"eat"};?????#?undef,?so?prints?""?and?raises?a?warning ?

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

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

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

    上下文

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

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

       
       
    1. my?$scalar?=?"Mendeleev";?

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

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

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

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

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

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

       
       
    1. my?$scalar?=?("Alpha",?"Pie");?#?Value?of?$scalar?is?now?"Pie"?

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

       
       
    1. my?@array?=?("Alpha",?"Pie"); ?
    2. my?$scalar?=?@array;?#?Value?of?$scalar?is?now?4?

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

       
       
    1. my?@array?=?("Alpha",?"Goo"); ?
    2. my?$scalar?=?"-X-"; ?
    3. print?@array;??????????????#?"AlphaBetaGoo"; ?
    4. print?$scalar,?@array,?98;?#?"-X-AlphaBetaGoo98";?

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

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

    引用和嵌套数据结构

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

       
       
    1. my?@outer?=?("Sun",?"Mercury",?"Venus",?undef,?"Mars"); ?
    2. my?@inner?=?("Earth",?"Moon"); ?
    3. $outer[3]?=?@inner; ?
    4. print?$outer[3];?#?"2" ?

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

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

    引用使用反斜杠创建。

       
       
    1. my?$colour????=?"Indigo"; ?
    2. my?$scalarRef?=?$colour; ?

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

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

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

       
       
    1. print?$$scalarRef;?#?"Indigo" ?

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

       
       
    1. my?@colours?=?("Red",?"Orange",?"Yellow",?"Green",?"Blue"); ?
    2. my?$arrayRef?=?@colours; ?
    3. ?
    4. print?$colours[0];???????#?direct?array?access ?
    5. print?${?$arrayRef?}[0];?#?use?the?reference?to?get?to?the?array ?
    6. print?$arrayRef->[0];????#?exactly?the?same?thing ?
    7. ?
    8. my?%atomicWeights?=?("Hydrogen"?=>?1.008,?"Helium"?=>?4.003,?"Manganese"?=>?54.94); ?
    9. my?$hashRef?=?%atomicWeights; ?
    10. ?
    11. print?$atomicWeights{"Helium"};?#?direct?hash?access ?
    12. print?${?$hashRef?}{"Helium"};??#?use?a?reference?to?get?to?the?hash ?
    13. print?$hashRef->{"Helium"};?????#?exactly?the?same?thing?-?this?is?very?common ?

    声明数据结构

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

       
       
    1. my?%owner1?=?( ?
    2. ????"name"?=>?"Santa?Claus",?
    3. ????"DOB"??=>?"1882-12-25",?
    4. ); ?
    5. ?
    6. my?$owner1Ref?=?%owner1; ?
    7. ?
    8. my?%owner2?=?( ?
    9. ????"name"?=>?"Mickey?Mouse",?
    10. ????"DOB"??=>?"1928-11-18",?
    11. ); ?
    12. ?
    13. my?$owner2Ref?=?%owner2; ?
    14. ?
    15. my?@owners?=?(?$owner1Ref,?$owner2Ref?); ?
    16. ?
    17. my?$ownersRef?=?@owners; ?
    18. ?
    19. my?%account?=?( ?
    20. ????"number"?=>?"12345678",?
    21. ????"opened"?=>?"2000-01-01",?
    22. ????"owners"?=>?$ownersRef,?
    23. ); ?

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

       
       
    1. my?%owner1?=?( ?
    2. ????"name"?=>?"Santa?Claus",?
    3. ); ?
    4. ?
    5. my?%owner2?=?( ?
    6. ????"name"?=>?"Mickey?Mouse",?
    7. ); ?
    8. ?
    9. my?@owners?=?(?%owner1,?%owner2?); ?
    10. ?
    11. my?%account?=?( ?
    12. ????"number"?=>?"12345678",?
    13. ????"owners"?=>?@owners,?
    14. ); ?

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

       
       
    1. #?Braces?denote?an?anonymous?hash ?
    2. my?$owner1Ref?=?{ ?
    3. ????"name"?=>?"Santa?Claus",?
    4. ????"DOB"??=>?"1882-12-25",?
    5. }; ?
    6. ?
    7. my?$owner2Ref?=?{ ?
    8. ????"name"?=>?"Mickey?Mouse",?
    9. ????"DOB"??=>?"1928-11-18",?
    10. }; ?
    11. ?
    12. #?Square?brackets?denote?an?anonymous?array ?
    13. my?$ownersRef?=?[?$owner1Ref,?$owner2Ref?]; ?
    14. ?
    15. my?%account?=?( ?
    16. ????"number"?=>?"12345678",?
    17. ); ?

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

       
       
    1. my?%account?=?( ?
    2. ????"number"?=>?"31415926",?
    3. ????"opened"?=>?"3000-01-01",?
    4. ????"owners"?=>?[ ?
    5. ????????{ ?
    6. ????????????"name"?=>?"Philip?Fry",?
    7. ????????????"DOB"??=>?"1974-08-06",?
    8. ????????},?
    9. ????????{ ?
    10. ????????????"name"?=>?"Hubert?Farnsworth",?
    11. ????????????"DOB"??=>?"2841-04-09",?
    12. ????],?
    13. ); ?

    获取信息的数据结构

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

       
       
    1. my?$ownersRef?=?$account{"owners"}; ?
    2. my?@owners????=?@{?$ownersRef?}; ?
    3. my?$owner1Ref?=?$owners[0]; ?
    4. my?%owner1????=?%{?$owner1Ref?}; ?
    5. my?$owner2Ref?=?$owners[1]; ?
    6. my?%owner2????=?%{?$owner2Ref?}; ?
    7. print?"Account?#",?$account{"number"},?"n"; ?
    8. print?"Opened?on?",?$account{"opened"},?"n"; ?
    9. print?"Joint?owners:n"; ?
    10. print?"t",?$owner1{"name"},?"?(born?",?$owner1{"DOB"},?")n"; ?
    11. print?"t",?$owner2{"name"},?$owner2{"DOB"},?")n";?

    或者,简称:

       
       
    1. my?@owners?=?@{?$account{"owners"}?}; ?
    2. my?%owner1?=?%{?$owners[0]?}; ?
    3. my?%owner2?=?%{?$owners[1]?}; ?
    4. print?"Account?#",?"n"; ?
    5. print?"Opened?on?",?"n"; ?
    6. print?"Joint?owners:n"; ?
    7. print?"t",?")n"; ?
    8. print?"t",?")n";?

    或使用引用和>运算符:

       
       
    1. my?$ownersRef?=?$account{"owners"}; ?
    2. my?$owner1Ref?=?$ownersRef->[0]; ?
    3. my?$owner2Ref?=?$ownersRef->[1]; ?
    4. print?"Account?#",?"n"; ?
    5. print?"Joint?owners:n"; ?
    6. print?"t",?$owner1Ref->{"name"},?$owner1Ref->{"DOB"},?")n"; ?
    7. print?"t",?$owner2Ref->{"name"},?$owner2Ref->{"DOB"},?")n";?

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

       
       
    1. print?"Account?#",?"n"; ?
    2. print?"Opened?on?",?"n"; ?
    3. print?"Joint?owners:n"; ?
    4. print?"t",?$account{"owners"}->[0]->{"name"},?$account{"owners"}->[0]->{"DOB"},?")n"; ?
    5. print?"t",?$account{"owners"}->[1]->{"name"},?$account{"owners"}->[1]->{"DOB"},?")n";?

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

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

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

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

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

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

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

    条件语句 

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

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

       
       
    1. my?$word?=?"antidisestablishmentarianism"; ?
    2. my?$strlen?=?length?$word; ?
    3. ?
    4. if($strlen?>=?15)?{ ?
    5. ????print?"'",?$word,?"'?is?a?very?long?word"; ?
    6. }?elsif(10?<=?$strlen?&&?$strlen?<?15)?{ ?
    7. ????print?"'",?"'?is?a?medium-length?word"; ?
    8. }?else?{ ?
    9. ????print?"'",?"'?is?a?a?short?word"; ?
    10. }?

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

       
       
    1. print?"'",?"'?is?actually?enormous"?if?$strlen?>=?20;?

    unless...else...

       
       
    1. my?$temperature?=?20; ?
    2. ?
    3. unless($temperature?>?30)?{ ?
    4. ????print?$temperature,?"?degrees?Celsius?is?not?very?hot"; ?
    5. }?else?{ ?
    6. ????print?$temperature,?"?degrees?Celsius?is?actually?pretty?hot"; ?
    7. }?

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

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

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

    三元运算符

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

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

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

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

    三元运算符可能被嵌套:

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

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

    循环

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

    常规的while循环:

       
       
    1. my?$i?=?0; ?
    2. while($i?<?scalar?@array)?{ ?
    3. ????print?$i,?":?",?$array[$i]; ?
    4. ????$i++; ?
    5. }?

    Perl同样支持until关键字:

       
       
    1. my?$i?=?0; ?
    2. until($i?>=?scalar?@array)?{ ?
    3. ????print?$i,?$array[$i]; ?
    4. ????$i++; ?
    5. }?

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

       
       
    1. my?$i?=?0; ?
    2. do?{ ?
    3. ????print?$i,?$array[$i]; ?
    4. ????$i++; ?
    5. }?while?($i?<?scalar?@array); ?

       
       
    1. my?$i?=?0; ?
    2. do?{ ?
    3. ????print?$i,?$array[$i]; ?
    4. ????$i++; ?
    5. }?until?($i?>=?scalar?@array);?

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

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

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

       
       
    1. foreach?my?$string?(?@array?)?{ ?
    2. ????print?$string; ?
    3. }?

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

       
       
    1. foreach?my?$i?(?0?..?$#array?)?{ ?
    2. ????print?$i,?$array[$i]; ?
    3. }?

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

       
       
    1. foreach?my?$key?(keys?%scientists)?{ ?
    2. ????print?$key,?$scientists{$key}; ?
    3. }?

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

       
       
    1. foreach?my?$key?(sort?keys?%scientists)?{ ?
    2. ????print?$key,?$scientists{$key}; ?
    3. }?

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

       
       
    1. print?$_?foreach?@array;?

    循环控制

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

       
       
    1. CANDIDATE:?for?my?$candidate?(?2?..?100?)?{ ?
    2. ????for?my?$divisor?(?2?..?sqrt?$candidate?)?{ ?
    3. ????????next?CANDIDATE?if?$candidate?%?$divisor?==?0; ?
    4. ????} ?
    5. ????print?$candidate."?is?primen"; ?

    数组相关函数

    在数组中修改

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

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

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

       
       
    1. print?pop?@stack;?#?"Charlie" ?
    2. print?@stack;?????#?"FredEileenDenise"?

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

       
       
    1. push?@stack,?"Bob",?"Alice"; ?
    2. print?@stack;?#?"FredEileenDeniseBobAlice"?

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

       
       
    1. print?shift?@stack;?#?"Fred" ?
    2. print?@stack;???????#?"EileenDeniseBobAlice"?

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

       
       
    1. unshift?@stack,?"Hank",?"Grace"; ?
    2. print?@stack;?#?"HankGraceEileenDeniseBobAlice"?

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

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

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

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

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

       
       
    1. my?@elements?=?("Antimony",?"Arsenic",?"Aluminum",?"Selenium"); ?
    2. print?@elements;?????????????#?"AntimonyArsenicAluminumSelenium" ?
    3. print?"@elements";???????????#?"Antimony?Arsenic?Aluminum?Selenium" ?
    4. print?join(",?",?@elements);?#?"Antimony,?Arsenic,?Aluminum,?Selenium"?

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

       
       
    1. print?reverse("Hello",?"World");????????#?"WorldHello" ?
    2. print?reverse("HelloWorld");????????????#?"HelloWorld" ?
    3. print?scalar?reverse("HelloWorld");?????#?"dlroWolleH" ?
    4. print?scalar?reverse("Hello",?"World");?#?"dlroWolleH"?

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

       
       
    1. my?@capitals?=?("Baton?Rouge",?"Indianapolis",?"Columbus",?"Montgomery",?"Helena",?"Denver",?"Boise"); ?
    2. ?
    3. print?join?",?map?{?uc?$_?}?@capitals; ?
    4. #?"BATON?ROUGE,?INDIANAPOLIS,?COLUMBUS,?MONTGOMERY,?HELENA,?DENVER,?BOISE"?

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

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

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

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

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

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

       
       
    1. my?@elevations?=?(19,?100,?98,?1056); ?
    2. ?
    3. print?join?",?sort?@elevations; ?
    4. #?"1,?1056,?19,?98"?

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

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

       
       
    1. print?join?",?sort?{?$a?cmp?$b?}?@elevations; ?
    2. #?"1,?98"?

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

       
       
    1. print?join?",?sort?{?$a?<=>?$b?}?@elevations; ?
    2. #?"1,?1056"?

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

       
       
    1. sub?comparator?{ ?
    2. ????#?lots?of?code... ?
    3. ????#?return?-1,?0?or?1 ?
    4. } ?
    5. ?
    6. print?join?",?sort?comparator?@elevations;?

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

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

    内置函数

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

    ·量很大

    ·非常有用

    ·拥有大量的文档

    ·在不同的语法中会有很大差异,所以请查阅文档

    ·有时候接受正则表达式作为参数

    ·有时候接受整个代码块作为参数

    ·有时候在参数之间不需要逗号

    ·有时候需要随意个数的逗号来分隔参数,有时候又不需要

    ·有时候可以自动匹配变量,如果只提供了很少一部分变量

    ·通常不需要使用括号包围参数,除非在模糊的语义环境中

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

    用户定义子程序

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

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

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

       
       
    1. sub?hyphenate?{ ?
    2. ?
    3. ??#?Extract?the?first?argument?from?the?array,?ignore?everything?else ?
    4. ??my?$word?=?shift?@_; ?
    5. ?
    6. ??#?An?overly?clever?list?comprehension ?
    7. ??$word?=?join?"-",?map?{?substr?$word,?$_,?1?}?(0?..?(length?$word)?-?1); ?
    8. ??return?$word; ?
    9. } ?
    10. ?
    11. print?hyphenate("exterminate");?#?"e-x-t-e-r-m-i-n-a-t-e"?

    参数拆包

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

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

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

       
       
    1. print?left_pad("hello",?10,?"+");?#?"+++++hello"?

    1.有些人不对参数进行拆包,而只是简单使用@_.这是错误并不被鼓励的。

       
       
    1. sub?left_pad?{ ?
    2. ????my?$newString?=?($_[2]?x?($_[1]?-?length?$_[0]))?.?$_[0]; ?
    3. ????return?$newString; ?
    4. } ?

    2.以下对@_的拆包方法,略低于强烈劝阻:

       
       
    1. sub?left_pad?{ ?
    2. ????my?$oldString?=?$_[0]; ?
    3. ????my?$width?????=?$_[1]; ?
    4. ????my?$padChar???=?$_[2]; ?
    5. ????my?$newString?=?($padChar?x?($width?-?length?$oldString))?.?$oldString; ?
    6. ????return?$newString; ?
    7. } ?

    3.通过使用shift删除数据来对@_拆包,这种方法对4个参数的时候推荐使用

       
       
    1. sub?left_pad?{ ?
    2. ????my?$oldString?=?shift?@_; ?
    3. ????my?$width?????=?shift?@_; ?
    4. ????my?$padChar???=?shift?@_; ?
    5. ????my?$newString?=?($padChar?x?($width?-?length?$oldString))?.?$oldString; ?
    6. ????return?$newString; ?
    7. } ?

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

       
       
    1. sub?left_pad?{ ?
    2. ????my?$oldString?=?shift; ?
    3. ????my?$width?????=?shift; ?
    4. ????my?$padChar???=?shift; ?
    5. ????my?$newString?=?($padChar?x?($width?-?length?$oldString))?.?$oldString; ?
    6. ????return?$newString; ?
    7. } ?

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

    4.你可以同时使用多个标量赋值,对@_整体进行拆包。同样,这对于4个参数的情况才可行:

       
       
    1. sub?left_pad?{ ?
    2. ????my?($oldString,?$width,?$padChar)?=?@_; ?
    3. ????my?$newString?=?($padChar?x?($width?-?length?$oldString))?.?$oldString; ?
    4. ????return?$newString; ?
    5. } ?

    5.对于带有很多个参数的子程序,或者某些参数是可选的,或者不能被用于和其他组合,最好的方法是需要用户在调用函数的时候,提供一个参数的哈希变量。对于这种方法,我们的子程序被调用的时候会有点不一样:

       
       
    1. print?left_pad("oldString"?=>?"pod",?"width"?=>?10,?"padChar"?=>?"+"); ?

    子程序如下:

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

    返回值

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

       
       
    1. sub?contextualSubroutine?{ ?
    2. ????#?Caller?wants?a?list.?Return?a?list ?
    3. ????return?("Everest",?"K2",?"Etna")?if?wantarray; ?
    4. ?
    5. ????#?Caller?wants?a?scalar.?Return?a?scalar ?
    6. ????return?3; ?
    7. } ?
    8. ?
    9. my?@array?=?contextualSubroutine(); ?
    10. print?@array;?#?"EverestK2Etna" ?
    11. ?
    12. my?$scalar?=?contextualSubroutine(); ?
    13. 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的返回值和$?返回的是同样的值:

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

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

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

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

       
       
    1. use?strict; ?
    2. use?warnings; ?
    3. ?
    4. print?@ARGV; ?
    5. exit?37;?

    文件和文件句柄

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

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

       
       
    1. my?$f?=?"text.txt"; ?
    2. my?$result?=?open?my?$fh,?"<",?$f; ?
    3. ?
    4. if(!$result)?{?die?"Couldn't?open?'".$f."'?for?reading?because:?".$!; ?
    5. }?

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

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

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

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

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

    使用 chomp去除换行符:

       
       
    1. chomp?$line;?

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

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

       
       
    1. while(!eof?$fh)?{ ?
    2. ????my?$line?=?readline?$fh; ?
    3. ????#?process?$line... ?
    4. }?

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

       
       
    1. while(my?$line?=?<$fh>)?{ ?
    2. ????#?process?$line... ?
    3. }?

    甚至是:

       
       
    1. while(<$fh>)?{ ?
    2. ????#?process?$_... ?
    3. }?

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

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

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

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

       
       
    1. close?$fh2; ?
    2. close?$fh;?

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

       
       
    1. my?$line?=?<STDIN>; ?

    等待用户的输入回车:

       
       
    1. <STDIN>;?

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

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

    文件测试

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

       
       
    1. 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。

       
       
    1. my?$string?=?"Hello?world"; ?
    2. if($string?=~?m/(w+)s+(w+)/)?{ ?
    3. ????print?"success"; ?
    4. }?

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

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

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

       
       
    1. my?$string?=?"colourless?green?ideas?sleep?furiously"; ?
    2. my?@matches?=?$string?=~?m/(w+)s+((w+)s+(w+))s+(w+)s+(w+)/; ?
    3. ?
    4. print?join?",?map?{?"'".$_."'"?}?@matches; ?
    5. #?prints?"'colourless',?'green?ideas',?'green',?'ideas',?'sleep',?'furiously'"?

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

       
       
    1. my?$string?=?"Good?morning?world"; ?
    2. $string?=~?s/world/Vietnam/; ?
    3. print?$string;?#?"Good?morning?Vietnam"?

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

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

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

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

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

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

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

       
       
    1. #?Try?once?without?/g. ?
    2. $string?=~?s/[aeiou]/r/; ?
    3. print?$string;?#?"r?tonne?of?feathers?or?a?tonne?of?bricks" ?
    4. ?
    5. #?Once?more. ?
    6. $string?=~?s/[aeiou]/r/; ?
    7. print?$string;?#?"r?trnne?of?feathers?or?a?tonne?of?bricks" ?
    8. ?
    9. #?And?do?all?the?rest?using?/g ?
    10. $string?=~?s/[aeiou]/r/g; ?
    11. print?$string,?"n";?#?"r?trnnr?rf?frrthrrs?rr?r?trnnr?rf?brrcks" ?

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

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

       
       
    1. "Hello?world"?=~?m/ ?
    2. ??(w+)?#?one?or?more?word?characters ?
    3. ??[?]???#?single?literal?space,?stored?inside?a?character?class ?
    4. ??world?#?literal?"world" ?
    5. /x; ?
    6. ?
    7. #?returns?true?

    模块和包

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

    模块

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

       
       
    1. use?strict; ?
    2. use?warnings; ?
    3. ?
    4. sub?zombify?{ ?
    5. ????my?$word?=?shift?@_; ?
    6. ????$word?=~?s/[aeiou]/r/g; ?
    7. ????return?$word; ?
    8. } ?
    9. ?
    10. return?1;?

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

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

       
       
    1. set?PERL5LIB=C:foobarbaz;%PERL5LIB% ?

    或者

       
       
    1. export?PERL5LIB=/foo/bar/baz:$PERL5LIB?

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

       
       
    1. use?strict; ?
    2. use?warnings; ?
    3. ?
    4. require?Demo::StringUtils; ?
    5. ?
    6. print?zombify("i?want?brains");?#?"r?wrnt?brrrns"?

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

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

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

       
       
    1. use?strict; ?
    2. use?warnings; ?
    3. ?
    4. sub?subroutine?{ ?
    5. ????print?"universe"; ?
    6. } ?
    7. ?
    8. package?Food::Potatoes; ?
    9. ?
    10. #?no?collision: ?
    11. sub?subroutine?{ ?
    12. ????print?"kingedward"; ?
    13. }?

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

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

       
       
    1. subroutine();?????????????????#?"kingedward" ?
    2. main::subroutine();???????????#?"universe" ?
    3. Food::Potatoes::subroutine();?#?"kingedward"?

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

       
       
    1. use?strict; ?
    2. use?warnings;package?Demo::StringUtils;sub?zombify?{ ?
    3. ????my?$word?=?shift?@_; ?
    4. ????$word?=~?s/[aeiou]/r/g; ?
    5. ????return?$word; ?
    6. } ?
    7. ?
    8. return?1; ?

    并且修改main.pl:

       
       
    1. use?strict; ?
    2. use?warnings; ?
    3. ?
    4. require?Demo::StringUtils; ?
    5. ?
    6. 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:

       
       
    1. use?strict; ?
    2. use?warnings; ?
    3. ?
    4. package?Animal; ?
    5. ?
    6. sub?eat?{ ?
    7. ????#?First?argument?is?always?the?object?to?act?upon. ?
    8. ????my?$self?=?shift?@_; ?
    9. ?
    10. ????foreach?my?$food?(?@_?)?{ ?
    11. ????????if($self->can_eat($food))?{ ?
    12. ????????????print?"Eating?",?$food; ?
    13. ????????}?else?{ ?
    14. ????????????print?"Can't?eat?",?$food; ?
    15. ????????} ?
    16. ????} ?
    17. } ?
    18. ?
    19. #?For?the?sake?of?argument,?assume?an?Animal?can?eat?anything. ?
    20. sub?can_eat?{ ?
    21. ????return?1; ?
    22. } ?
    23. ?
    24. return?1;?

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

       
       
    1. require?Animal; ?
    2. ?
    3. my?$animal?=?{ ?
    4. ????"legs"???=>?4,?
    5. ????"colour"?=>?"brown",?
    6. };???????????????????????#?$animal?is?an?ordinary?hash?reference ?
    7. print?ref?$animal;???????#?"HASH" ?
    8. bless?$animal,?"Animal";?#?now?it?is?an?object?of?class?"Animal" ?
    9. print?ref?$animal;???????#?"Animal"?

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

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

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

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

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

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

    构造函数

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

       
       
    1. use?strict; ?
    2. use?warnings; ?
    3. ?
    4. package?Animal; ?
    5. ?
    6. sub?new?{ ?
    7. ????my?$class?=?shift?@_; ?
    8. ????return?bless?{?"legs"?=>?4,?"colour"?=>?"brown"?},?$class; ?
    9. } ?
    10. ?
    11. #?...etc. ?

    通过如下方式使用:

       
       
    1. my?$animal?=?Animal->new();?

    继承

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

       
       
    1. use?strict; ?
    2. use?warnings; ?
    3. ?
    4. package?Koala; ?
    5. ?
    6. #?Inherit?from?Animal ?
    7. use?parent?("Animal"); ?
    8. ?
    9. #?Override?one?method ?
    10. sub?can_eat?{ ?
    11. ????my?$self?=?shift?@_;?#?Not?used.?You?could?just?put?"shift?@_;"?here ?
    12. ????my?$food?=?shift?@_; ?
    13. ????return?$food?eq?"eucalyptus"; ?
    14. } ?
    15. ?
    16. return?1; ?

    样例代码:

       
       
    1. use?strict; ?
    2. use?warnings; ?
    3. ?
    4. require?Koala; ?
    5. ?
    6. my?$koala?=?Koala->new(); ?
    7. ?
    8. $koala->eat("insects",?"eucalyptus");?#?eat?only?the?eucalyptus?

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

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

    BEGIN语句块

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

       
       
    1. use?strict; ?
    2. use?warnings; ?
    3. ?
    4. print?"This?gets?printed?second"; ?
    5. ?
    6. BEGIN?{ ?
    7. ????print?"This?gets?printed?first"; ?
    8. } ?
    9. ?
    10. print?"This?gets?printed?third";?

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

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

       
       
    1. use?strict; ?
    2. use?warnings; ?
    3. ?
    4. print?"This?'print'?statement?gets?parsed?successfully?but?never?executed"; ?
    5. ?
    6. BEGIN?{ ?
    7. ????print?"This?gets?printed?first"; ?
    8. } ?
    9. ?
    10. print?"This,?also,?is?parsed?successfully?but?never?executed"; ?
    11. ?
    12. ...because?e4h8v3oitv8h4o8gch3o84c3?there?is?a?huge?parsing?error?down?here.?

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

       
       
    1. if(0)?{ ?
    2. ????BEGIN?{ ?
    3. ????????print?"This?will?definitely?get?printed"; ?
    4. ????} ?
    5. ????print?"Even?though?this?won't"; ?
    6. }?

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

       
       
    1. BEGIN?{ ?
    2. ????if($condition)?{ ?
    3. ????????#?etc. ?
    4. ????} ?
    5. }?

    use

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

    以下3个语句:

       
       
    1. use?Caterpillar?("crawl",?"pupate"); ?
    2. use?Caterpillar?(); ?
    3. use?Caterpillar;?

    跟下面是等价的:

       
       
    1. BEGIN?{ ?
    2. ????require?Caterpillar; ?
    3. ????Caterpillar->import("crawl",?"pupate"); ?
    4. } ?
    5. BEGIN?{ ?
    6. ????require?Caterpillar; ?
    7. } ?
    8. BEGIN?{ ?
    9. ????require?Caterpillar; ?
    10. ????Caterpillar->import(); ?
    11. }?

    ·三个例子并不是顺序有误。这只是因为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的代码:

       
       
    1. use?strict; ?
    2. use?warnings; ?
    3. ?
    4. package?Caterpillar; ?
    5. ?
    6. #?Inherit?from?Exporter ?
    7. use?parent?("Exporter"); ?
    8. ?
    9. sub?crawl??{?print?"inch?inch";???} ?
    10. sub?eat????{?print?"chomp?chomp";?} ?
    11. sub?pupate?{?print?"bloop?bloop";?} ?
    12. ?
    13. our?@EXPORT_OK?=?("crawl",?"eat"); ?
    14. ?
    15. return?1; ?

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

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

       
       
    1. use?strict; ?
    2. use?warnings; ?
    3. use?Caterpillar?("crawl"); ?
    4. ?
    5. crawl();?#?"inch?inch" ?

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

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

       
       
    1. use?strict; ?
    2. use?warnings; ?
    3. use?Caterpillar?();?#?no?subroutines?named,?no?import()?call?made ?
    4. ?
    5. #?and?yet... ?
    6. Caterpillar::crawl();??#?"inch?inch" ?
    7. Caterpillar::eat();????#?"chomp?chomp" ?
    8. Caterpillar::pupate();?#?"bloop?bloop"?

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

    @EXPORT

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

       
       
    1. use?strict; ?
    2. use?warnings; ?
    3. ?
    4. package?Caterpillar; ?
    5. ?
    6. #?Inherit?from?Exporter ?
    7. use?parent?("Exporter"); ?
    8. ?
    9. sub?crawl??{?print?"inch?inch";???} ?
    10. sub?eat????{?print?"chomp?chomp";?} ?
    11. sub?pupate?{?print?"bloop?bloop";?} ?
    12. ?
    13. our?@EXPORT?=?("crawl",?"eat",?"pupate"); ?
    14. ?
    15. return?1;?

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

       
       
    1. use?strict; ?
    2. use?warnings; ?
    3. use?Caterpillar;?#?calls?import()?with?no?arguments ?
    4. ?
    5. crawl();??#?"inch?inch" ?
    6. eat();????#?"chomp?chomp" ?
    7. 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语句中看到:

       
       
    1. 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"就派上用场。

    注意!很多内置函数可以被调用,并不需指定参数,使它们以$_来代替。希望这个可以让你更好的理解这个格式:

       
       
    1. print?foreach?@array;?

    还有

       
       
    1. foreach?(?@array?)?{ ?
    2. ????next?unless?defined; ?
    3. }?

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

    英文原文:Learn Perl in about 2 hours 30 minutes

    原文链接:http://www.oschina.net/translate/learn-perl-in-2-hours

    【编辑推荐】

    1. Perl模块用法指南
    2. Perl入门之道:执行最常见的编程任务
    3. Perl、PHP、Python、Java和Ruby的比较
    4. JavaScript是新的Perl?
    5. Perl 5.16.3 发布,安全补丁版本

    (编辑:李大同)

    【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容!

      推荐文章
        热点阅读