Perl书籍介绍之Learning Perl
2014-08-14 15:59:49 阿炯

这是Perl系列书籍中的常青树,perl程序员的入门必读之作。中文译名《Perl语言入门》。


如果刚开始学习 perl 这门语言,那么本书就是你所需要的----不管你是一名程序员,还是系统管理员,抑或是网络黑客。这本书被整整两代 perl用户昵称为"小骆驼书",本书根据作者从 1991 年开始的教学经验积累汇聚而成,多年来十分畅销。

perl 能在绝大多数平台上完成几乎所有任务,不管是简单的修修补补,还是大型完备的网络应用。本书从最基础的开始教起,然后逐渐深入,让你慢慢能够自行编写多至 128 行的程序,如今 90% 的 perl程序差不多都是这般大小。本书每章都包含若干习题,帮助巩固消化刚学到的知识。也许其他书籍只是想着灌输 perl编程的条条框框,但本书不同,我们希望把你培养成一名真正的 perl 程序员。

主题包括
 perl 的数据结构和变量类型
 子程序
 文件的操作
 正则表达式
 字符串操作(包括 unicode)
 列表和排序
 进程管理
 智能匹配
 第三方模块的使用

实际上每章的内容并不多,我们把它控制在一两个小时内能够读完的篇幅。每章后面都附有若干习题,帮助你巩固刚学到的知识,在附录A中还附有习题解答,供你比对参考。因此本书可说是相当适合作为“Perl入门”的课堂教材来使用。我们对此有第一手的实践经验,几乎所有内容都是逐字逐句从我们的“Learning Perl”课程教学中萃取出来的,这门招牌课程已经经过了上千名学生的实践检验。当然,除了课堂教学以外,拿来自学也是非常不错的。

虽然Perl是活生生的“Unix工具箱”,但你并不需要成为Unix大师,甚至也不必精通Unix就可以使用本书。除非特别注明,否则我们所提到的一切都可以应用到Windows版本的ActivePerl(ActiveState公司出品)、Strawberry Perl以及许许多多其他流行的Perl版本上。

阅读本书之前,虽然无需具备任何Perl基础,但我们还是衷心希望你能够对写程序的基本概念有所了解,像变量(variable)、循环(loop)、子程序(subroutine)和数组(array)以及最重要的“用你最熟悉的文本编辑器来编辑源代码”这类事情。我们不会花时间说明这些概念。有些人平生所学的第一个程序语言就是Perl,并因学习本书而获得成功。我们很高兴能看到这样的事例,但我们也无法保证每个人都能取得一样的成功。

作者:Randal L.Schwartz brian d foy Tom Phoenix  
支持的格式:PDF
页码:384

FreeOA站长的《Perl语言入门.第6-8版》学习笔记(截止于2024年2月)

第6版,介绍了基于v5.14版本的Perl;于2012年出版。

第7版,介绍了基于v5.24版本的Perl;于2016年出版。

第8版,介绍了基于v5.34版本的Perl;于2022年出版。

办法不止一种。
There's More Than One Way To Do It.

--Perl

Perl脚本编译运行

perl解释器能一次完成编译和运行这两个动作。运行程序时,Perl内部的编译器会先载入各个源程序,将之转换成内部使用的bytecode,这是一种Perl在内部用来表示程序语法树的数据结构,然后交给Perl的bytecode引擎运行。

Ch2

v5.22新增了十六进制浮点数直接量的写法,和用e表示以10为幂的写法类似,用p表示以2为幂。
0x1f.op3

十六进制的浮点数直接量就是Perl内部保存数字时所使用的形式?与C一样,不建议对负数取位、取模等操作,会导致结果不一致。

Perl内部总是按“双精度浮点数(double-precision floating-point)”的要求来保存和运算,且此类转换是默认自动发生的。另有一个integer编译指令(pragma),某些运算会将浮点数据强制转换为整数。

从v5.6开始,直接支持非十进制的整数直接量:0b,二进制;0,八进制;0x,十六进制。另有hex()与oct()函数对特写的数制进行转换,可以自写一个bin()函数来对二进制数的处理。前置零(leading zero)的表示法只对直接量有效,不能用于字符串到数字的自动转换,自动转换总是按十进制数字来处理。

v5.34开始还可以用0o作为八进制数的前导标志。

0377    #十进制数字255的八进制写法
'0377'    #会转换成十进制数字377

my $v=0b00110;
printf "0b%b\n",$v;
printf "%#b\n",$v;

>0b110

alias d2b="perl -e 'printf qq|%#b\n|, int(shift)'"

要注意:Perl在自动转换时总是把字符串转换成十进制数,上述两个函数只接受字符串参数,如果将其参数写为数字直接量的话,结果就会出错。其会将参数转换为字符串,然后再转为其他进制的数字。

Perl完全支持Unicode,但因历史原因其不会自动将源代码当作Unicode编码的文本文件读入,所以想要在源代码中使用Unicode书写直接量的话,就得手工加上utf8的编译指令(告诉编译器如何解释,值得终身养成的好习惯),另外还得确保以UTF-8编码的方式保存源文件。

标量变量与其它类型的变量(数组、哈希)相比是其单存储一个值的变量;其可以包含undef、数值、字符串、引用。

代码点(code point)可以直接在双绰号内用\x{}的形式表示。

use v5.32;
use utf8;
binmode(STDOUT, ':encoding(UTF-8)');
say "\N{U+56FD}";    #'国'字的Unicode代码点
say chr(0x56fd);
say "\x{2668}";    #十六进制的Unicode代码点,名称为'HOT SPRINGS',U+2744表示雪花
say "\N{SNOWMAN}";    #同上,只是用了对应的名称来书写

除了代码点外,Unicode字符还有属于自己的名字,如果待输入的字符很难通过键盘录入,又不记得对应的代码点,那就用其那较长的名字好了。自带模块charnames就是用来支持通过名字表示Unicode字符的:只要在双引号上下文中把名字放在\N{...}中即可。

Perl和C中的操作符的优先级与结合性都是相同的。

要判某个字串是undef而不是空字符串,可以使用defined函数;如果是undef则该函数返回假。如果从来没有对标量变量进行过赋值,那么其值就是undef;当然也可以直接为变量赋undef,让其从头再来。

列表(list)指的是标量的有序集合,而数组(array)则是存储列表的变量。前者指的是数据,而后者指的是变量。列表的值不一定要放在数组里,但每个数组变量都一定包含一个列表。他们中的每元素都是单独的标量变量,拥有独立的标量值,且是有序的。

编译指令(pragma)不过是提供给编译器某些指示,告诉它如何处理接下来的代码行为。

&subname
如果该子程序和Perl内置函数同名,就必须使用与号调用,只能在没有同名内置函数的情况下省略与号。

用wantarry函数来判断子程序是在标量上下文还是列表上下文件环境。


Ch3

Perl中的范围操作符(..)只能从小到大依次累加,且累加是整数1。

v5.12之后支持一种新写法:...
表示此为占位符,代码可通过编译,但运行到此处时就会抛出错误提示有未完成的代码,另有表示范围的操作符也是三个点;也叫做yada yada操作符。

同样从v5.12的each操作符在用于啥希的基础上也可以用于数组类型了,其返回数组的索引与元素值。

清空数组的正确方法:
use v5.32;
use Data::Dumper;

my $str='I love Freeoa.';
my @nar=('a',1,'b',2,'c');
say '@arr1:',Dumper(\@nar,$str);
say '-' x 16;

#undef $str;
#undef @nar;    #正确清空
@nar=();    #正确清空
#@nar=undef;    #错误清空
say '@arr2:',Dumper(\@nar,$str);
say '-' x 16;

undef($nar[2]);    #另类的错误清空
say '@arr3:',Dumper(\@nar,$str);

v5.20引入了子程序签名(subroutine signature)的实验功能,与原型(prototypes)解决类似的问题。其用于帮助检查传入函数的参数数量的。

v5.28开始解除了自v5.10对state变量无法对数组或哈希初始的限制。如下是一侧优化过的(斐波那契,Fibonacci)数列计算方法
use v5.28;

sub next_fib{
    state @numbs=(0,1);
    push @numbs,$numbs[-2]+$numbs[-1];
    return $numbs[-1];
}

say next_fib() for (1..99);


Ch5

while(defined($_=<STDIN>)){
    chomp;
    print "Has saw $_\n";
}

唯独while循环的条件表达式里只有行输入操作符的前提下,这个简写才起作用。

while与for循环对行输入操作处理方式上不一样,后者在循环开始执行前必须将输入全部读进来,而前者是一行行的处理。

<>(钻石操作符)
提供了一种读取输入的方法,只有在碰到所有输入的结尾时才会返回undef(即跳出while循环)。

perl命令行模式下使用连字符(-)作参数,表示要从标准输入读取数据,这点与大多数unix标准工具程序(cat,sed)类似(不存在会跳到下一个文件上而不会退出)。
$ARGV:命令行参数代表的文件列表中,当前被处理的文件名
@ARGV:命令行参数数组
$ARGV[n]:命令行参数数组的元素
ARGV:表示<>当前正在处理的文件句柄

v5.22引入了双钻石操作符:<<>>
解决在传入参数的过程中若文件名中特殊字符,如'|',会开启管道操作并启动外部进程,可能带来安全问题;因此引入<<>>,与<>功能相同但不再开启外部程序。

运行时动态生成格式字符串:
perl -e 'printf "The items are:\n" . ("%14s\n" x @ARGV),@ARGV;'


# 数值运算符: <, >, <=, >=, ==, !=, <=>, +, *
# 字符串运算符: lt, gt, le, ge, eq, ne, cmp, ., x

双箭头符号 '=>' 被称为 'fat comma'(胖逗号),因为它与逗号完全等价。

Perl没有私有方法,习惯上在希望私有的方法名前面有一个或者两个下划线。

一个比较优秀的printf示例:按标准长度列出字符数组
my @its=qw(wilma dino freeoa prebbles);
my $fat="The items are:\n" . ("%14s\n" x scalar(@its));
printf $fat,@its;

或者两行解决:
printf "The items are:\n" . ("%14s\n" x @its),@its;

或一行解决:
perl -e 'printf "The items are:\n" . ("%14s\n" x @ARGV),@ARGV;' freeoa net hto abc123

v5.6之前所有的文件句柄名称都是祼字(bareword),v5.6起可以将文件句柄放到常规的标量变量中,该变量同样遵循变量的命名规则,官方建议祼字为全大写。有6个保留的特殊的文件句柄:STDIN,STDOUT,STDERR,DATA,ARGV,ARGVOUT。这些句柄在Perl程序启动时就自动打开就位了(perl进程的父进程(shell)),其源自"标准I/O"函数库。

三参数的open可以很方便的指定编码层(layer)

文件句柄指定编码时的区别:':encoding(UTF-8)'与':utf8'
后者可以认为是前者的简写,简写方式不会考虑输入或输出的数据是否真的是否是合法的UTF-8字符串,直接当作UTF-8编码的字符串来处理,有时会出现一些问题;而前者会确认编码是否正确且还有其它字符集可选。

列出系统中所有perl能处理的编码清单:
perl -MEncode -le "print for Encode->encodings(':all')"

这个层(layer)还可以进行数据格式的转换
如unix(\n)转到windows下的(\r\n)CR-LF

open FREEOA, '>:crlf', $win_file_name;

perl5.6开始支持用binmode关闭换行符相关的处理(直接以二进制方式处理):
binmode STDOUT, ':encoding(UTF-8)';

如果传到标准输入的是UTF-8编码的字符,那么应用事先告诉Perl按该编码处理(除非提前关闭告警):
binmod STDIN, ':encoding(UTF-8)';

Perl自动关闭文件句柄是在Perl程序正常运行结束的情况下,如果中途出了状况这个自动关闭就很难说了。关闭句柄时会刷新输出缓冲并释放该文件上的锁,也有有助于缓解OS同时打开文件句柄数量上限。

通过select来选择(默认)句柄,设置'$|=1'可关闭文件句柄不再缓冲数据

open的pragmas(编译指令)的一些用法:

通常有两种指定编码的方式:一是在打开文件句柄时在模式部分指定;二是使用binmode操作符作用于句柄。

use open IN, ':encoding(UTF-8)';
use open OUT => ':encoding(UTF-8)';

use open IN => ":crlf", OUT => ":bytes";

#输入和输出使用同样的编码方式
use open IO => ":encoding(UTF-8)";
use open ':encoding(UTF-8)';

默认文件句柄已经处于打开状态,所以需要使用:std子编译指令来让改用之前设定的编码方式:
use open ':std';

在用print输出的文件句柄时,为帮其判断标量变量的句柄,可用{}围住句柄:
print {$fhd[1]} "FreeOA.Net";

但也带来了副作用,默认为会打印$_的内容时就必须显式的指定了。


Ch6

使用reverse直接反转现在啥希来得到一个新的反序啥希(前提是现Hash的Value也是唯一的):my %new_h=reverse %orig_h;

v5.26前在标量上下文中,哈希返回的一个分数(数量与分配桶相关:n/m),而现在返回是的其键数。


Ch7-8

Perl正则在运行时先完成变量内插,取得表示模式的字符串后,再编译该正则表达式。

v5.12增加了另一个“能匹配除换行符外任意单个字符”的写法:\N,以替换点号(.)。

模式匹配中的捕获组(capture group)

$_="yabba dabba doo";
if(/y((.)(.)\3\2) d\1){
    print ...
}

从v5.10开始支持一种反向引用写法:\g{N}的形式,取代了前面的\N的方式,防止语义上的歧义。
$_="aa11bb";
if(/(.)\111/)

if(/(.)\g{1}11/)

这样还可以使用负数来反向引用。

正则在从ASCII到Unicode的转变过程中,如何区分前后不同意义的字符集成为一个需要解决的问题,v5.14中引入一种新的修饰符(/a),严格按老的ASCII字符语义的范围来匹配时。v5.6之前\s仅能匹配换页符、水平制表符、换行符、回车及空格符本身:[\f\t\b\r]。Unicode下的符号为\p{Space},则是\s的超集(v5.24能匹配25种空格),大写的P则表示反向:\P{Space};\p{Digit}与\d基本相同(v5.24能匹配550个不同的数字符)。

v5.10还增加了范围更小的空白字符集:\h只匹配水平空白符,而\v则只匹配垂直空白符;把\h和\v并起来就成了\p{Space}。\R匹配任意一种换行符,不管是\r\n还是\n。

v5.26及其后对左花括号'{'一改无需转义的要求,虽在v5.28中有所放宽,但v5.30又对此要求重新开启。右花括号一直没有要求转义。但尖括号(<>)在做被用作定界符要注意对右尖括号的转义。

修饰符/s会把模式中出现的所有.都修改成能匹配任意字符,若只其中几个点号匹配任意字符可以换用字符集[^\n],v5.12开始引入了\N来表示\n的否定意义。v5.26中引入了/xx修饰符来扩充/x,允许在匹配的字符集中加入空格但其并不参与匹配(但也会因此失去加注释的机会)。
/[abc 123] /x        #匹配a,b,c,1,2,3或空格
/ [abc 123] /xx        #匹配a,b,c,1,2,3


为模式选择一种字符解释(编码)方式

v5.14开始增加了一些用于通知Perl如何解释字符意义的修饰符,着重于两个重要方面:对大小写的处理以及对字符集合简写的阐释。

总共有三种字符解释方式:ASCII、Unicode和locale。最后一种(也只有这一种)经常会引发一些问题。修饰符/a告诉Perl采取ASCII方式,而lu则表示采取Unicode方式,最后一种/l表示遵从本地化语言的设定,按照对应的字符集编码作相应处理。如果不提供这类修饰符,Perl会根据perlre文档中描述的方式采取最为妥贴的行为。而通过使用修饰符,你可以显式指定程序确切的行为。

首先来谈谈字符集的简写。之前已经看到过/a修饰符的用法,它会告诉Per1仅在ASCII范围内阐释\w、ld和\s等字符集简写。修饰符/u会告诉Perl对上面这些简写采取更为宽泛的匹配方式,只要是Unicode范围内定义的单词、数字、空白,都能匹配。修饰符/l告诉Perl按照本地化设定阐释简写的意义,所以任何一个本地设定的字符编码认为是单词的字符,都会被\w简写匹配。所以当使用某项简写并希望按照特定语义对其进行解释的话,可以选择恰当的修饰符作出声明:

use v5.14;
/w+/a    #仅仅是A-Z、a-z、0-9、_这些字符
/w+/u    #任何Unicode当中定义为单词的字符
/w+/1    #类同于ASCII的版本,但要应对本地化环境的复杂处理

单词锚位

单词边界锚位(\b),匹配单词(\w)的首尾。
\bword
word\b
\bword\b

非单词边界锚(\B),可匹配所有\b不能匹配的位置。
\bword\B

一般不用担心出现在模式内的定界符,因其是成对出现的,即使其中含有引号;但尖括号(<与>)不是正则表达式的元字符,所以它们不可能成对出现,因此使用其时需要在其前加了反斜线来转义或使用其它符号来定界。

模式内插

my $what='freeoa';
while(<>){
    if(/\A($what)/){
        ...
    }
}

捕获变量

($what)的圆括号并不多余且非常重要,因为不加()可能会与$what中的字符产生冲突。当然圆括号的另一个用途是触发正则捕获并临时保存($N)所匹配的字符串。这是正则非常重要的功能,可用于提取字符串中某些特定部分。

$N与\N、\{N}有些类似,内容基本上是一样的,\N是反向引用的模式匹配期间的结果,而$N是模式匹配结束后对得到的捕获内容的索引。

my $sentence = 'The quick brown fox jumped over the lazy dog';
my $substring = 'quick.*fox';
#$sentence =~ s{$substring}{big bad wolf};
#$sentence =~ s{\Q$substring\E}{big bad wolf};
my $quoted_substring = quotemeta($substring);
$sentence =~ s{$quoted_substring}{big bad wolf};

这些捕获变量通常能存活到下次成功匹配为止,是下次成功匹配。这也是为什么模式匹配部是出现在if或while条件表达式中的原因。不想麻烦建议将这些$N存放到变量中。

不捕获(noncapturing parentheses)模式
在左括号后面加上问号和冒号(?:)来告诉perl这一对圆括号完全是为了分组而存在的。

这种数字编号与内容之间的匹配问题确定不太好的把握,5.10增加了对捕获内容的直接命名,最终捕获内容会保存在特殊的啥希%+里,键就是在捕获时的特殊标签,而对应值为被捕获的字串:(?<LABEL>PATTERN),LABEL可自行命名。

my $ns='FreeOA or Hto';
if($ns =~ /(?<n1>\w+)\s(?:and|or)\s(?<n2>\w+)/){
    say "Got $+{n1} & $+{n2}";
}

在使用捕获标签后,反向引用的用法也随之变化,之前用\1或\g{1}的写法也可以升级为\g{label}此类的写法。

my $ms='Freeoa Stone and Hto Stone';
if($ms=~/(?<ln>\w+)\sand\s\w+\g{ln}/){
    say "Got $+{ln}";
}

my $ms='Freeoa Stone and Hto Stone';
#if($ms=~/(?<ln>\w+)\s(and|or)\s\w+\s\g{ln}/){
if($ms=~/(?<ln>\w+)\s(and|or)\s\w+\s\k{ln}/){
    say "Got $+{ln}";
}

可以使用\k{label}这种反向引用来等同于\g{label}。

另外不要忘记非常有历史意义的特殊正则变量:$&,$`,$',实在不想记可引入English模块来正常变量化这些。

v5.10引入的修饰符/p只会针对特定的正则表达式开启类似的自动捕获变量,但它们的名字不再(依然可用)是$`,$&和$',而是用${^PREMATCH}、${^MATCH}和${^POSTMATCH}表示。

v5.22开始支持对分组括号都无需捕获的修饰符:/n,可将正则表达式中所有的圆括号变更为非捕获分组。

一些题词:* = {0,},+ = {1,} ? = {0,1}

正则表达式优先级
正则表达式特性    示例
圆括号(分组或捕获)        (…),(?:…),(?<LABEL>…)
量词        a*,at,a?,a{n,m}
锚位和序列        abc,^,$,\A,\b,\z,\Z
择一竖线        a|b|c
原子        a,[abc],\d,\1,\g{2}

在理解复杂模式时,加上一些()会对弄清优先级有好处,但也因此带来捕获效果,因此可用非捕获圆括号来分组。

v5.34支持对量词的{0,n}可简写为{,n},无最小匹配次数。


Ch9

与m//,qw//一样,s///也可以使用不同的定界符。一般没有左右之分(非成对)的用法与斜线一样,只要重复3次即可。但如果使用有左右之分的成对字符,就必须使用两对:一对包住模式,一对包住替换字符串;甚至可以是非成对的字符来构造定界符。

s{freeoa}{net}
s[freeoa](net)
s<freeoa>#net#

无损替换(同时保留原始和替换后的字串)
传统的做法是先复制一份再行替换:
(my ($copy=$original) =~ s///;

v5.14增加了一个/r的修饰符,专门解决此问题:原先的s///操作完成后返回的是成功替换的次数,加上/r后就会保留原字串中的值,而把替换结果作为返回值。
my $copy=$original =~ s///r;

字符串的大小写转换操作符(\u,\l,\U,\L)也可由内置函数lc,uc,lcfirst,ucfirst来实现;另外在对比Unicode字串时,因部分字符的映射规则不同,会导致比对出现异常,5.16使用'\F'转义符来提示后续字符串按映射表处理(foldcase),其对应的内置函数为fc。另外还有对元字符本身可能导致混乱的转义操作符:\Q,其也有对应的函数实现:quotemeta。\E字符用于终结由\U,\L,\Q所作用的字符串或变量范围。

my ($rs,$rep,$s1)=('((FreeOA','((');
my $s='My site is ((FreeOA.Net.';

#$s1=$rs=~s/\Q$rep\E(FreeOA)/$1/r;
#$s1=$rs=~s/\Q((\E(FreeOA)/$1/r;

#my $qr=quotemeta($rep);
#$s1=$s=~s/$qr//gr;    
#$s1=$s=~s/\Q$rep\E//gr;


非贪婪模式:*?,+?,{N,}?,??

join EXPR,LIST
其中LIST中必须要有两个以上的元素;且EXPR是字符串非模式。

一些智能的模式:
v5.22增加了基于Unicode新单词边界符:探视周围最符合语义的单词,系\b的延伸,增加花括号并给出边界类型短语(\b{wb}):
use v5.22;
my $s="this doesn't correctly.";
#$s=~s/\b(\w+)/\u$1/g;    #This Doesn'T Correctly.
$s=~s/\b{wb}(\w+)/\u$1/g;    #This Doesn't Correctly.

同期还有一个新的单词边界符:\b{sb},推断某个标点是句子的未尾,还是类似"Mr. Zheng"这样的缩略句。

v5.24增加了行边界符(\b{lb}),基于启发式(大部分时间会按阁下的想法实现)推断合理的行与行的边界,以免换行时打断某个单词,或在单词内的标点处换行,或在其它不该换行的空格处换行。在正确的句子未尾加入一个换行符:
$string=~s/(.{50,75}\b{lb}/$1\n/g;

v5.26添加了特殊的@{^CAPTURE}数组变量来保存(所有|最后?)捕获变量;其第一个元素与$&相同(整个匹配),其余元素与捕获缓冲区顺序相同。


Ch10

perl有5种循环:for,foreach,while,until及裸块。last操作符对整个循环块起作用,只能会对当前运行中的最内层的循环块发挥作用(嵌套循环块),next亦是如此。

&&,||,//,?:都有一个共性:根据左边的值决定是否计算右边的表达式,即部分求值(partial-evaluation)操作符,因此可以算作优良的控制结构设计。


Ch11

use Modlue qw/ /;
use Modlue ();

使用了模块,但不导入任何新函数。但依然可以使用全名称的方式引用该模块中的函数。

查看指定版本的内置模块及其版本信息
libmodule-corelist-perl

use Module::CoreList;
my %mods = %{$Module::CoreList::version{5.028}};
say Dumper(\%mods);

非标准默认安装路径需用lib编译指令来加载对应的安装路径:
use lib MOD_LIST;

v5.26开始,当前目录不再是模块搜索路径的一部分(安全原因,与操作系统的配置一样,路径PATH变量中有当前目录时而这些的基线不再合规)。另搭配FindBin模块可提供辅助:
use FindBin qw($Bin);
use lib "$Bin/../lib";


Ch12

'_'虚拟句柄的作用。

v5.10开始允许使用栈式(stack)写法将文件测试符排成一行:
-r -w -x $file;

文件测试符-r,-w,-x,-o测试的是有效用户或组的ID,这里的有效用户指的是“负责”运行这个程序的人;相应的-R,-W,-X,-O测试使用的是实际用户或组的ID;尤其是程序被设定了set-XID的时候。Unix中仅有的7种文件类型也分别对应的7种检测方式:-f,-d,-l,-S,-p,-b,-c。

将unix timestamp转为当前时间(localtime)。
my $uts=1180630098;
my $dt=localtime $uts;
say $dt;

gmtime是世界标准时间(格林威治)。

time()直接返回本机当前时间。

Perl有一个双值变量(dualvar)的概念。标量可以同时有两个版本的值,一个数字类型的,一个字符串类型的。大部分时候这没有啥问题:如系统错误变量$!的字符串版本是错误消息描述,数字版本是错误代号。如Scalar::Util模块。v5.22中启用了bitwise特性会令位操作符将操作数统一按数字类型计算。

use v5.22;
use feature qw(bitwise);

my ($num,$num_str,$str)=(137,'137','Amelia');

say "num_str & str:",$num_str & $str;
say "num & str:",$num & $str;
say "num & num_str:",$num & $num_str;
say "num_str & str:",$num_str & $str;

如果以字符串方式进行位运算,bitwise增加了一个新的位操作符写法:即后补一个'.':say "num & str:",$num &. $str;

本站的《Perl取得文件详细信息》中有对stat及其权限的二进制算法有一定的说明。


Ch13

v5.12开始改变了将表示假值的空字串或undef作为参数付给chdir就回到了主目录,只需直接调用不传任何参数就行。像'~'这种由shell提供的快捷方式在perl中并不支持,因Perl是直接与OS通信。

文件名通配的隐式语法
@all_files=<*>;
@all_files=glob "*";

<>内出现的亦是替换为值,类似于双引号内的字符串的变量内插。用法示例:
@files=<FRED/*>;    #文件名通配操作
@lines=<FRED>;
@lines=<$fred>;    #从文件句柄读取
$name='FRED';
@files=<$name/*>    #文件名通配操作

当<>内仅是一个简单的标量变量时,这就是间接文件句柄读取(indircet filehandle read),其中变量的值就是待读取的文件句柄名称。在编译阶段决定是文件名配操作还是从文件句柄记取,因此和变量内容无关。

目录句柄中跳过.打头的文件:
next if $name =~ /^\./;
next if $name =~ /\A\./;

opendir中的文件名是不包含路径的。要让程序更具可移植性,用File::Spec::Functions模块来构造用于本地系统的文件名:
$fullname=catfile($dirname,$name);

Path::Class也是不错的选择,尽管要通过CPAN来安装才可以使用。


Ch14

use v5.32;
use utf8;

binmode(STDIN,':encoding(utf8)');
binmode(STDOUT,':encoding(utf8)');

my $strl='some very long string.';
my $strr=substr($strl,index($strl,'l'));
say $strr;

my $strx='Module::CoreList provides information on which core each version of perl.';
say $strx;
substr($strx,8,8)='Unknow';
say '-' x 59;
say $strx;

my $strc='入门级别 2:《Intermediate Perl》即羊驼。';
substr($strc,2,2)='高手';
say $strc;

my $previous_value = substr $strc,2,2,'抄手';    #替换后返回被替换的'高手';

光'use utf8'还是不行的,还的将默认的输出句柄的编码设置为utf8,否则会有如下的警告:
Wide character in say at pstr1.pl line

通过探测perl的版本来决定应用是否开启utf8特性:
utf8 => ($^V ge v5.8.1 ? 1 : 0)

命令行参数与编码
特殊变量@ARGV的值取自于命令行参数,这些参数输入时参考的是本地环境设置,所以需要取得该设置并按其编码方式解码(终端是UTF-8):
use v5.24;
use utf8;
use Encode;
use I18N::Langinfo qw(langinfo CODESET);

binmode(STDIN,':encoding(utf8)');
binmode(STDOUT,':encoding(utf8)');
say "@ARGV";
my @NARGV;

my $curcode=langinfo(CODESET);
for my $arg (@ARGV){
    push @NARGV,decode $curcode,$arg;
}
say "@NARGV";

perl freeoa.net.pl abc 123 第三参数
>abc 123 第三参数


还可以用绑定操作符(=~)只对字串的某部分进行操作。
my $rstr='It is some perl articles on freeoa.net, on sites freeoa has more freeoa.net';
substr($rstr,-30)=~s/freeoa/OaFree/g;

substr与index能做到的事用正则也能办到,通常前者要快一点,因为其没有正则表达式的额外的负担:后者总是区分大小写,不必担心元字符,也不会动用任何内存变量。

sub fmat{
    my $num=shift;
    1 while $num =~ s/^(-?\d+)(\d{3})/$1,$2/;
    return $num;
}


1 while (...)

相当于

while(...){
    1;
}

语句的重点是在while后的处理逻辑中,其它的只是辅助。

可见:简洁与可读性是矛盾的,甚至包括性能在内。

编程语言比拼的最后目的其实就是两种:提高硬件的运行效率和提高程序员的开发效率,遗憾的是这两点无法并存的!

将字串中的'_'替换为',',下面提供了3种方法:

$_='1_23_56_89_';

while($_=~s/_/,/){
    1;
}

1 while $_=~s/_/,/;

s/_/,/g;

say $_;


习题三:查子字符串
use v5.32;
use utf8;

binmode(STDIN,':encoding(UTF-8)');
binmode(STDOUT,':encoding(UTF-8)');

my ($str,$sstr,@places);
print '请输入字符串:';
chomp($str=<STDIN>);
print '输入待查子串:';
chomp($sstr=<STDIN>);

for(my $pos=-1;;){
    $pos=index($str,$sstr,$pos+1);
    last if $pos==-1;
    push @places,$pos;
}

$" = ',';
say "子串\'$sstr\'在\'$str\'的:@places";

>
请输入字符串:将正则内置的语言应该是好语言
输入待查子串:语言
子串'语言'在'将正则内置的语言应该是好语言'的:6,12


Ch16

Perl给进程发信号,使用信息名称或其编号皆可。
kill 2,PID or die "can not signal to PID with sigit:$!";

kill 'INT',PID ...    #用字串代替数字编号

kill INT=>PID ...    #信号号称自动作为祼字字符

编号为0的特殊信号:向进程向是否存活的测试信号

unless(kill 0,$pid){
    warn "$pid has gone!";
}else{
    say "$pid alived.";
}

大部分时候,Perl会一直等到安全妥当的时候,才会处理进来的信号;比如在分配内存和调整内部数据结构的阶段是不会理睬大多数信号。但会立即处理SGIILL,SIGBUS,SIGSEGV这些信号,这些信号可能会造成不安全因素。

对程序收到的信息进行统计:
use v5.20;
sub hdsig_hup{state $n;say 'Got Hup:',++$n}
sub hdsig_usr1{state $n;say 'Got Usr1:',++$n}
sub hdsig_usr2{state $n;say 'Got Usr2:',++$n}
sub hdsig_int{say 'Caught Int. Exiting...:';exit}

say "Pid is:$$";

foreach my $sig(qw(int hup usr1 usr2)){
    $SIG{uc $sig}="hdsig_${sig}";
}

while(1){sleep 1};

v5.20引入了键-值对切片,可一次取出多个键-值对,之前的哈希切片:
my @vs=@hash{@vget};

哈希前使用的@表示需要返回一组值,这组值最后保存到数组@vs内;如果要同步提取对应的键,还额外再配对成新的哈希:
my %nhash;
@nhash{@ps}=@vs;
或:
my %nhash=map{$_=>$hash{$_}} @ps;

v5.20的新提出语法如下(不再借助于之前的map函数操作):
use v5.20;
my %nhash=%hash{@ps};

这里变量名前的符号并非表示变量类型,只是用它指定提取数据的方式。在此的%表示按哈希键-值对的方式返回,从而构建出一个新的分片后的哈希。同理,也可对数组变量如此操作,净数组下标当作哈希键返回:
my %first_last_scores=%scores[0,-1];

对数组变量scores使用%的意图同上,之所以能看出是对数组变量的操作,是因为其后面使用[]来定义其下标清单。


Ch17

eval处理异常

use v5.32;
my ($f,$d)=(13,0);
my $r=eval{ $f/$d } // 'NaN';
say $r;
print "Some Error:$@" if $@;

#直接简洁测试
say "Error:$@" unless(eval{$f/$d});


#从指定文件中查找所包含的指定内容,后将相关行保存到数组中
use v5.32;
die "No Arg for file Gvied!" unless (@ARGV);

#my @m=grep { chomp; /71$/ } <ARGV>;
my @m=grep chomp && /71$/, <ARGV>;

say "@m";

perl解释器的编译器会在解析源代码时捕获错误并在运行脚本前停止,而eval仅能捕获perl运行时的出现的错误。

v5.10.1起,Perl自带了autodie编译指令,可以让程序自动化抛出异常。其默认会对一系列的用于处理文件、句柄、IPC和套接字的函数自动化施行监管;不过可以自行指定需要由autodie监控的操作符:
use autodie qw( open system :socket );

List::Util现已为内置模块,列表数组处理比方便。

附录ABC

对新版本新特性的使用。
针对特写版本号
use v5.24;
use 5.24.0;    #$^V
use 5.024;    #$]

use feature qw(:5:20);
use feature qw(state signatures);

禁用新版本的所有实验特性:
no feature qw(:all);

关闭实验特性的警告:
no warnings qw(experimental::signature);
no warnings qw(experimental);    #全部

PDL(Perl Data Language)模块为Perl的数学计算提供无限的可能,POSIX模块中也包含了部分此类功能函数。

一些好的代码片段

跳过隐藏目录
open FREEOA,'.' or die;
foreach (sort readdit FREEOA){
    next if /\A\./;
    ...
}

close(FREEOA);

列出某目录的文件的访问时间(Access Time)与修改时间(Modify Time)

use v5.20;

die "Dir $ARGV[0] you gived is correct?" unless(-d $ARGV[0]);
chdir($ARGV[0]);

foreach my $f (glob('*')){
    next unless(-f $f);    #只对文件做检查
    my ($atime,$mtime)=map{
        my ($year,$month,$day)=(localtime($_))[5,4,3];
        $year+=1900;$month+=1;
        sprintf('%4d-%02d-%02d',$year,$month,$day);
    } (stat $f)[8,9];
    printf("%-39s Atime:%10s,Mtime:%10s\n",$f,$atime,$mtime);
}

接上,引入了POSIX::strftime来格式化时间

use v5.20;
use POSIX qw(strftime);

die "Dir $ARGV[0] you gived is correct?" unless(-d $ARGV[0]);
chdir($ARGV[0]);

foreach my $f (glob('*')){
    next unless(-f $f);    #只对文件做检查
    my ($atime,$mtime)=map{
        strftime('%y-%m-%d',localtime($_));
    } (stat $f)[8,9];
    printf("%-39s Atime:%8s,Mtime:%8s\n",$f,$atime,$mtime);
}



Pdf版本:
Perl语言入门.第4版.中文版

Perl语言入门.第5版.英文版

Perl语言入门.第5版.中文版

Perl语言入门.第6版.英文版

Perl语言入门.第6版.中文版

Perl语言入门.第6版.英文版.习题集

Perl语言入门.第7版.英文版