Learning Perl Notes

2022/5/25

page 21~29

Perl 程序并不需要用什么特殊的文件名或扩展名,甚至不用扩展名就最好不要用。

Perl 总是将版本号看作是三位数表示的,如use 5.010

Perl 的注释是从井号(#)开始到行尾结束。

好多Perl程序可能根本不用子程序。

Perl程序并不需要变量声明的部分。

换行符\n(newline character).

分号的目的是分隔不同的Perl语句,而不是断行。

让后续调用跳过编译,将程序代码编译后驻留到内存中。

perldoc命令,用来阅读Perl及其相关扩展和工具程序的说明文档。

第二章

p30~33

Perl内部,总是按“双精度浮点数”的要求保存数字并进行运算的,也就是说,Perl内部并不存在整数值。

IEEE-745格式中,双精度能表示15位的精度。

Perl允许在整数直接量中插入下划线,将若干位数分开。如61_298_040_283_768
为了避免歧义,没有使用逗号。

八进制0开头,十六进制0x开头,二进制0b开头。

双星号表示乘幂: 2**3 表示2的3次方。

空字符在Perl中没有特殊意义。

Perl中字符串的长度没有限制。

Perl完全支持Unicode。

想要在源代码中使用Unicode书写直接量的话,需要手工加上utf8编译指令 use utf8; .

附录C Unicode入门

Unicode字符集(Unicode Character Set).是字符(character)到代码点(code point)的抽象关系映射。

可以不用考虑操作系统的区别。


查询的资料


参考1
参考2


代码点

code point,指一个编码表中的某个字符对应的代码值,也就是Unicode编码表中每个字符对应的数值,Unicode中,代码点用十六进制书写,并加上U+前缀,如字符A对应的编码值是U+0041.

ASCII码

用一个字节表示字符,一个字节有八位256种状态。

一共定义了128个字符,只是用了一字节的后七位,最前面一位统一规定为0.

问题

128个字符无法表示完其他语言。

Unicode出现

提供一种标准方案来展示世界上所有语言中的所有字符。

Unicode只是一个字符集,没有规定字符对应的二进制码如何存储。这样导致的问题是,不知道用2个字节表示的是一个字符还是两个字符。

为了解决Unicode的编码问题,诞生了UTF-8(变长编码)和UTF-16,还有UTF-32(定长编码),它们都是编码方式。

UTF-8

实现了对ASCII码的向后兼容。

UTF-8是使用最广泛的一种Unicode编码方式。

最大特点是可变长。根据字符的不同变换长度。

编码规则:
1. 对于单个字节的字符,第一位设为 0,后面的 7 位对应这个字符的 Unicode 码点。因此,对于英文中的 0 - 127 号字符,与 ASCII 码完全相同。这意味着 ASCII 码那个年代的文档用 UTF-8 编码打开完全没有问题。
2. 对于需要使用 N 个字节来表示的字符(N > 1),第一个字节的前 N 位都设为 1,第 N + 1 位设为0,剩余的 N - 1 个字节的前两位都设位 10,剩下的二进制位则使用这个字符的 Unicode 码点来填充

UTF-16

一个概念,“平面”。

Unicode定义全世界字符时,通过分区定义,每个区可以存放65536个(2^16)字符。目前一共有17个(2^4+1)平面,因此,现在整个Unicode字符集的大小是2^21

基本平面(BMP), U+0000U+FFFF, 所有常见的字符都在这个平面。
辅助平面(SMP), U+010000
U+10FFFF, 包含剩下的字符。


UTF-8

UCS Transformation Format 8-bit,的缩写。是一种编码方式。


来源故事


使用Windows,最好选用UTF-16.

字素(grapheme)就是字形(glyph). 同一个字素的构成方式可能不止一种,也许只用了一个字符,也许组合了多个字符。

字素的分解和组合形式.
组合形式,多个字符合并为单个字符并用一个代码点表示。

表示同一个字素的几个字符构成了相应的分解版本。

计算机关心的是字符而不是字素。

打开UTF-8编译指令:
use utf8;
其告诉perl解释器按照UTF-8编码,方式解析源代码。

Unicode编码除了代码点外还有名字,如

2022/5/26

p34~p52

单引号和双引号之间的字符串不同。

  • 单引号中,除了单引号和反斜线字符外,单引号内所有字符都代表他们自己。只有在反斜线后接续单引号或者反斜线时,才表示转义。
  • 双引号中,反斜线可以转义许多控制字符。

字符串操作符

  • concatenation operator, 连接操作符,.”hello” . “world”
    • String repetition operator, 字符串重复操作符,x”fred” x 3.重复操作符的左操作数必然是字符串类型。

数字和字符串会根据操作符自动转换。
大多数时候不用关心数字和字符串的区别。

Perl的内置警告信息:

#!usr/bin/perl
use warnings;

利用diagnostics获取跟详细的问题描述。

#!/usr/bin/perl
use diagnostics;

一种优化程序的方法,关闭警告信息。

-M参数开启diagnostics

$perl -Mdiagnostics ./my_program

标量变量的名称以$开头,这个符号也称魔符“sigil”,然后是Perl标识符,区分大小写。如$name$Name, 并不局限于用ASCII字符。

魔符的含义是“取单个东西”。

大部分变量名习惯使用全小写。全大写一般为表示特殊意义的Perl保留变量。

perlvar文档可查询所有Perl特殊变量的名称。

双目赋值操作符:
$fred += 5; $str .= “ ”;

变量内插:
使用双引号可以把标量变量替换为其当前值。如

$meal = “brontosaurus steak”;

$barney = “fred ate a ${meal}”;    #$barney 现在是“fred ate a brontosaurus steak”

打印变量值不必使用变量内插的方式,如print $fred.

用花括号{}将变量名围起来以避免歧义。如:

$what = “brontosaurus steak”
print “Fred ate 3 $(what)s./n”;

使用代码点(code point)创建字符.

  • chr()函数,将代码点转换为对应字符。
  • ord()函数,将字符转换为代码点。

操作符的优先级,所有同时在Perl和C里出现的操作符,它们的优先级和结合性都是相通的。

优先级相同时,通过结合性判断。在顺序不确定是,就用括号。

字符串的比较操作符:
eq, ne, lt, gt, le, ge。

if控制结构,一定要用表示界限的花括号。

字符串‘0’和0是同一个标量值。

获取用户输入:
<STDIN> 其返回的字符串一般在末尾都会带有换行符。

使用chomp()操作符去除末尾换行符。其只能作用于单个变量,且变量的内容必须为字符串。只删除一个换行符。

chomp()函数返回实际移除的字符数。

undef值
被用作数字时表现为0,被用作字符串时表现为空字符串。Perl通常会对undef的值发出警告。

defined函数
用于判断某个字符串时undef还是空字符串。如果是undef,返回假,否则返回真。

创建undef值
使用undef操作符。如

$madonna = under; #回到虚无,仿佛从未用过。

p53~69

列表和数组表示复数(plural).

列表(list)是标量的有序集合,数组(array)是存储列表的变量,更精确的说,列表是数据,数组是变量。

数组和列表可以包含任意多个元素。

Perl“去除不必要的限制”的哲学理念。

数组的名称空间和标量的名称空间是完全分开的。

负数索引,从数组尾端往回计数。

列表直接量
由圆括号内用逗号隔开的一串数据表示,这些数据称为列表元素。
(1,2,3)

范围操作符
..(1..100) #100个整数构成的列表, 该操作符会从左边的数字计数到右边,每次加一。

qw简写
quoted word, 加上引号的单词,Perl会将其当成单引号内的字符串处理,如:

(“fred”, “barney”, “betty”, “wilma”, “dino”)
#同
( fred barney betty Wilma dino)

qw算是一种引用的形式,所以不能将注释放在qw列表中,常单独成行,如:

qw(
    fred
    barney
    betty
    wilma
    dino
)    

也可以用除圆括号以外的定界符。

Perl的座右铭是“There’s More Than One Way To Do It”

列表可赋值。

@字符
@rocks可读作“所有的rocks”.

@rocks = qw(bedrock slate lava);

$表示标量和@表示数组的由来

Larry宣称美元符号看起来像$calar,at看起来像@rray。
pop和push操作符
Perl并不擅长使用索引值来访问数组。
pop负责取出数组中最后一个元素并将其作为返回值返回。

pop也常用来删除数组中的最后一个元素。

Perl的一个惯例:只要不会因为拿掉括号而改变愿意,括号就是可省略的。

push,添加一个元素(或一串元素)到数组的尾端。

pop和push都只能用来操作数组。

shift和unshift操作符
操作数组的开头。

$m = shift(@array);
unshift(@array, 5);

splice操作符
添加或移除数组中间的某些元素。

在双引号中若要显示@符号,加上’\’来转义。

foreach控制结构
循环,能逐项遍历列表中的值。

@rock = qw/ bedrock slate lava /;
foreach $rock (@rocks) {
    ...
}

Perl最喜欢用的默认变量:$_

foreach (1..10) {   #默认用$_作为控制变量
    print “I can count to $_!\n”;
}

$_ = “Yabba dab a doo\n”
print;  #默认打印$_变量的值

reverse操作符
读取列表或数组的值,并按相反的次序返回该列表。不会修改传进来的参数。

sort操作符
根据内部的字符编码顺序对它们进行排序。

each操作符

标量上下文与列表上下文
概念: 同一个表达式出现在不同的地方会有不同的意义。

2022/5/27

p69~72

在列表上下文中使用产生标量的表达式
将undef赋值给数组并不会清空该数组。

强制指定标量上下文
用伪函数scalar。它不是真正的函数,只不过告诉Perl这里要切换到标量上下文。

使用Control+D告知操作系统,不会再有任何输入。

将数组交给chomp,可以去掉每个元素的换行符。

Perl会分配富裕的内存来节省事后的操作时间。

编程技巧:
直接打印输入

第四章 子程序

p73~

子程序(subroutine).

子程序名属于独立的名称空间。

定义子程序
关键字sub,子程序名和花括号。如:

sub marine {
    $n += 1;
    print “Hello, sailor number $n!\n”;
}

在Perl中,一般不区分有返回值的函数(function)和无返回值的过程(procesure).

子程序一般不需要事先声明。

子程序的定义是全局的。

使用&调用子程序,通常加上一对括号,如:

&(marine);

通常把对子程序的调用称为calling子程序。

返回值
在Perl中,所有的子程序都有一个返回值。

子程序最后一次运算的结果就是返回值。

返回的是最后执行的表达式的结果,但并非是代码的最后一行。

如:

sub sum_of_fred_and_barey {
print “Hey, you called the sum_of_fred_and_barney subroutine!\n”;
$fred + $barney; #这不是返回值!
print “Hey, I’m returning a value now!\n”;
}

返回值为1.

参数

argument。

Perl会自动将参数列表化名为特殊的数组变量@_,该变量在子程序执行时有效。

子程序中的私有变量

默认情况下,Perl里面所有的变量都是全局变量。

使用my操作符来创建私有变量,称之为词法变量(lexical variable).

Perl中,分号的作用是分隔语句,而不是必须的语句结尾标志。

my($m, $n) = @_;    #对子程序的参数命名

这一行语句会创建私有变量并为它们赋值。

这是一个列表上下文.

变长参数列表

自检参数列表:

sub max {
    If (@_ != 2) {
        Print “WARNING!”
    }
    .
    .
    .
}

空参数列表

词法变量my
可在任何语句块中使用。
注意,my操作符在没加括号时,只能用来声明单个词法变量。

在日常的Perl编程中,最好对每个新变量都使用my声明

第四章 子程序

区分子程序和内置函数,子程序是由用户定义的,而函数则不一定.

一般的变量都是全局变量.

要注意检查返回值。

特殊的数组变量@_用于存放参数列表。

my 操作符,创建私有变量。

直接使用数组“名称”来取得数组元素的个数。

pop and push, shift and unshift

pop and push do things to the end of an array.

shift and unshift do things to the start of an array.

The if Control Structure

The if control structure must have those block curly braces around the conditional code.

foreach 也必须要有花括号.

Notes on Lexical (my) Variables

Those lexical variables can actully be used in any block.

区分:

1
2
my($num) = @_;
my $num = @_;

上下文含义不同。

最好对每一个新变量都使用my声明,

Perl中的默认变量$_.

如果编译器在调用子程序前看到过子程序的定义或者Perl通过语法规则判断它只能是子程序调用,就可以省略&.

持久性私有变量

Using state to define.

不能在列表上下文中初始化数组和哈希类型的state变量.

Using Ctrl-D to stop getting values from .

Exercise

One string can be seen as one element of an array.

Can’t use defined(@array).

检查一个变量是否初始化

1
if (defined $var) 

检查一个数组是否为空

1
if (@array) {return};

CHAPTER 5 Input and Output

需要知道那些情况会返回undef.

判断是否到文件末尾

1
while (defined($line = <STDIN>))

or

1
while (<STDIN>)

There is no connection between the line-input operator and Perl’s favorite default variable.

Input from the Diamond Operator

The diamond operator: <>, it’s actually a special kind of line-input operator.

1
./mytac file

and the mytac file:

1
print reverse <>;

前面的 <STDIN> 是从标准输入获取, 毕竟 <> 里面是 STDIN, 单纯的 <> 就是从用户指定的位置读取.
file:

1
2
3
one
two
three

will print:

1
2
3
three
two
one

The invocation arguments 大抵是命令行参数。

- 连字符通常代表标准输入流。

钻石操作符从指定位置读取,如命令行参数。

不加参数时,chomp会直接作用在$_上.

The invocation arguments 存储在@ARGV中.

可重新初始化@ARGV:

1
@ARGV = qw# larry moe curly #;

Perl把数组内插到字符串中时,会在每个元素之间加上空格。

为何输出结果先发送至缓冲区
访问磁盘缓慢且效率低.

print <>; 相当于Unix下的cat命令.

print sort <>; 相当于Unix下的sort命令.

一条规则:假如它看起来像函数调用,它就是一个函数调用.

print

区分:print @array;print "@array";

The front one print a list of items, the next one print a string. 后者有分隔符. 都输出了数组中的全部元素, 只不过后者以空格分隔, 前者连在一起.

用printf格式化输出

要输出恰当的数字形式,可以使用%g, 可以把g看作”Good conversion for this number”

printf最常用在字段式的数据输出上,指定字段宽度,默认为右对齐。

A * inside the format string takes the next argument as a width:

Arrays and printf

1
2
printf "The items are:\n".("%10s\n" x @items), @items;
printf "%*s", 10, "wilma";

Filehandles

A name of a connection(和文件名区分). The connection is between your Perl process and the outside world.

感觉可以把这个看作link. 和文件操作符也挺像的.

Recommend to use all uppercase letters in the name of your filehandle.

Six special filehandle nemes:

1
2
3
4
5
6
STDIN
STDOUT
STDERR
DATA
ARGV
ARGVOUT

打开filehandle

建议全用大写字母来命名文件句柄.

都是行处理模式.

使用open operator.

1
open CONFIG, '<dino';

后面的是文件名, <, <<, >, >> 表示输入输出.
or

1
2
my $selected_output = 'my_output';
open LOG, "> $selected_output";

三参数写法 (最好写成这种):

1
open BEDROCK, '<', $file_name;

以特定编码写数据到某个文件:

1
open LOG, '>>:encoding(UTF-8)', $file_name;

layer, 层的概念和编码转换略有不同,我们可以选择不同的层叠加起来(就是因为能叠加才叫做层),产生不同的效果.

判断执行:

1
2
3
4
my $success = open LOG, '>>', 'logfile';
if (! $success) {
...
}

open的返回值,如果为真则为成功,为假则为失败。

关闭filehandle

1
close BEDROCK;

Small Conclusion
filehandle, 用于在Perl程序中操作外部文件。

用die处理致命错误

die 命令能中止程序并发出错误信息告知原因。

1
2
3
if (! open LOG, '>>', 'logfile') {
die "Cannot create logfile: $!";
}

$! 为特殊变量,存放解释性的系统错误信息。只有在系统服务请求失败后才有用。

程序名保存在特殊变量$0中。

若在die后面加上换行符,就不会显示行号和文件名。

用warn送出警告信息

产生警告信息,不会中止程序.

自动检测致命错误

1
use autodie;

使用filehandle

1
2
3
4
5
6
7
8
9
10
my @line = <STDIN>;
if (! open PASSWD, "/etc/passwd") {
die "How did you get logged in? ($!)";
}

while (<PASSWD>) {
chomp;
...
}

Using in print:

1
print LOG "Captain's log, stardate 3.14159\n";

改变默认的文件输出句柄

print 会默认输出到 STDOUT 中.

使用select:

1
2
3
select BEDROCK;
print "I hope";

也就是说, 这里的 print 就会输出到 BEDROCK 中.
特殊变量$|设置为1,使默认句柄在每次进行输出操作后立刻刷新缓冲区。

用say函数输出

会自动添加换行符。

标量变量中的filehandle

裸字bareword, 即像STDIN这种直接使用,而不是存储在变量中.

使用前需确保变量为空,以用来存放filehandle. 有人喜欢在变量名后面添加_fh

1
2
3
4
my @line = <STDIN>;
my $rocks_fh;
open $rocks_fh, '<', 'rocks.txt';

使用花括号,Perl会知道这个变量是filehandle: print { $rock_fh };

Exercise

<STDIN> 只接受一行的输入。

从标准输入中获取多行:

1
my @line = <STDIN>;

print 的一种用法:

1
print "the first string ", ", the second string ";

CHAPTER 6 Hash

哈希是一种数据结构。
和数组的区别:

1. 可以容纳任意多的值
2. 用名字检索,键和值,键唯一。
3. 没有顺序。

键总会被转换为字符串。

哈希是从awk语言中引入的.

哈希是从键到值的单行道,无法从值反推出其键.

访问哈希元素

1
$hash{$some_key}

注意,使用的是花括号.

赋值

1
2
$family_name{'fred'} = 'flintstone';
$family_name{'barney'} = 'rubble';

哈希有自己的名称空间.

美元符号和花括号,显示一个哈希元素。

赋值时会创建哈希元素

1
$family_name{'wilma'} = 'flintstone';

增加了一个新的键值对.

访问整个哈希

赋值, 必须是偶数个:

1
%some_hash = ('foo', 35, 'bar', 12.4);

这里应该只能用 () 包裹.

在列表上下文中,哈希的值是简单的键-值对列表.

unwinding the hash – turning it back into a list of key-value pairs.

1
@any_array = %some_hash;

顺序可能错乱.

Hash Assignment

1
my %new_hash = %old_hash;

Perl 会先unwind the %old_hash into a list of key-vaule pairs, 然后赋给%new_hash.

1
my %inverse_hash = reverse %any_hash;

键值会反转.

一个Perl的rule: The last in wins.

The Big Arrow

The Big Arrow (=>), it’s just a different way to “spell” a comma. 在Perl中,可以使用胖箭头代替逗号.

1
2
3
4
5
my %last_name = ( # a hash may be a lexical variable
'fred' => 'flintstone',
'dino' => undef,
'barney' => 'rubble',
)

在使用fat array时,可以省略键两侧的引号:

1
2
3
4
5
my %last_name = ( # a hash may be a lexical variable
fred => 'flintstone',
dino => undef,
barney => 'rubble',
)

Simple string without quote marks is called a bareword.

Hash Function

在数组上下文中:
keys function, return a list of all the keys in a hash.

values function, return a list of corresponding values.

1
2
3
4
my %hash = ('a' => 1, 'b' => 2, 'c' => 3,);
my @k = keys %hash;
my @v = values %hash;

在标量上下文中:
返回数量。

1
my $count = keys %hash; 

将哈希作为Boolean:

1
2
3
if (%hash) [
print "That was a true value!\n";
]

当至少有一对时为真.

The each Function

1
2
3
while ( ($key, $value) = each %hash ) {
print "$key => $value\n";
}

The exists Function

To see whether a key exists in the hash.

1
2
3
if (exists $books{"dino"}) {
print "Hey, there's a library card for dino!\n";
}

The delete Function

The delete function removes the given key (and its corresponding value).

1
2
my $person = "betty";
delete $books{$person};

Hash Element Interpolation

The magical charaacters that need blackslashing in double quotes: $ and @, " and \.

The %ENV hash

%ENV hash stores the info of environment.
You can see a PATH key in %ENV:

1
print "PATH is $ENV{PATH}\n";

Exercise

The chomp function is vital to get input from <STDIN>.

给哈希赋值使用的是()是列表,使用哈希的时候用{}花括号.

把未定义的值当成数字使用时,Perl会自动将它转换成0.

不能对哈希使用push等操作。

使用my声明多个变量:

1
my(@words, %count, $word);

CHAPTER 7 Regular Expressions

1
2
3
4
$_ = "yabba dabba doo";
if (/abba/) {
print "It matched!\n";
}

在字符串中匹配到其中的一部分。

Interpolating a variable into a pattern:

1
2
3
4
5
6
7
8
9
while ( <STDIN> ) {
chomp;
if ( /$ARGV[0]/ ) {
print "\tMatches\n";
}
else {
print "\tDoesn't match\n";
}
}

The Wildcard

The dot . matches any single character except a newline. \N也有同样的效果。

The backslash\也是metacharacter.

Unicode属性

匹配属性

1
\p{PROPERTY}

p 改为大写表示相反的含义。

Quantifier

*+.

?.

Anchors

\A anchor matches at the absolute beginning of a string.

\z matches the end of the string.
\Z allows an optional newline after it.

^ caret, match the beginning of line.
$ match the end of line.

Word Anchors

\b.

like:

1
/\bfred\b/

It matches at the start or end of a group of \w characters.

The nonword-boundary anchor
\B.

模式分组

使用小括号(). 圆括号也是元字符。

back reference
为什么叫 back reference , 可能是要往回看括号里面的内容,所以是“back”.

用括号捕获,反斜线加数字引用。引用的含义是再次匹配括号的内容。要忽略嵌套.

通过左括号判断次序。

新的写法:\g{N}, N为组号。使用perl5.10。

relative back reference相对反向引用。
使用负数,从当前位置往前数.

择一匹配

竖线|.

字符集

character class, [].

脱字符^.

字符集的简写

\d 表示任意一个数字。

\R匹配任意一种断行符。

\w匹配单词字符。

\s匹配空白字符。

Exercise

反向引用引用的是匹配到的值。

CHAPTER 8 Matching with Regular Expressions

Matches with m//

/pattern/ is a short cut of m/pattern/, pattern match operator. 模式匹配。

可以使用不同的分界符:

1
2
3
4
m(fred)
m<fred>
m{fred}
...

使用/pattern/可省略m.

Match Modifier

Sometimes called flags.

Case-Insentive Matching with /i

Using /i modifier.

Matching Any Character with /s

.能够匹配换行符。Using /s.

v5.12新增\N代表\n的补集。

Adding Whitespace with \x

让正则表达式中的whitespace不起作用:

1
/ -? [0-9]+ \.? [0-9] /x

不会匹配其中的空格.如果要匹配whitespace可以使用\s或escape a literal space.

Combining Option Modifiers

使用多个modifier, like:

1
2
3
if (/barney.*fred/is) {
...
}

Choosing a Character Interpretation

在 Perl v5.14 中添加的特性,选择解释方式like:

1
2
3
/\w+/a  #use the ASCII interpretation of character classes
/\w+/u #use Unicode
/\w+/l #respect the locale

case fold
In Unicode, lowercasing is not one-to-one.

只对应ASCII字符的大小写:

1
/k/aai

用两个\amodifier.

使用chr() to ensure we get the right bit pattern regardless of the encoding issues:

1
2
3
4
5
$_ = <STDIN>;
my $OE = chr( 0xBC );
if (/$OE/i) {
print "Found $OE\n";
}

Beginning and End-of-Line Anchors

如果没有\m那么^$就和\A\Z一样.

The Binding Operator =~

正则表达式默认作用于$_.

不再使用 default string , using =~ tells Perl to match the pattern on the right against the string on the left:

1
2
3
4
my $some_other = "I dream of betty rubbls,";
if ($some_other =~ /\brub\b) {
print "Aye, there's the rub.\n";
}

If there’s no binding operator, the expression uses $_ by default.

只有<STDIN>单独出现在控制语句中,$_才会自动存储.

1
my $likes_perl = <STDIN> =~ /\byes\b/i;

The Match Variables

Each regular expression capture holds part of the original string, not part of the pattern.

They are named like $1.

The difference of \4 and $4
\4 refers back to the capture during the pattern while it is trying to match.

$4 refers to the capture of an already completed pattern match.

一个是匹配时用, 一个是匹配后用.

Empty string 和 undef 是不同的,if you have three or fewer sets of parentheses in the pattern, $4 will be undef.

The Persistence of Captures

These capture variables generally stay around until the next successful pattern match.

If you need a capture for more than a few lines, it’s generally best to copy it into an ordinary variable.

1
my $wilma_word = $1;

Noncapturing Parentheses

Adding ?: after the opening parenthesis, which tell Perl you only want to use these parenthesis fo grouping.

1
2
3
if (/(?:bronto)?saurus (steak|burger)/) {
print "Fred wants a $1\n";
}

使用\nflag, which is added in v5.22, 可以把所有括号转换为noncapturing groups.

命名match variable, adding in Perl v5.10.
匹配到的内容存放在hash%+中:

在regex中用?<LABEL>PATTERN 也就是指定标记而不是 1, 2, 3…

1
2
3
4
5
6
use v5.10;

my $names = 'Fred or Barney';
if ( $names =~ m/(?<name1>\w+) (?:and|or) (?<name2>\w+)/) {
say "I saw $+{name1} and $+{name2}";
}

使用\g{lable} to back reference. 因为没有数字标记.

The Automatic Match Variables

为了避免和自己命名的容易重合,many of Perl’s build-in variables 具有奇怪的名字.

Three automatic match variables: $&, $上飘 and $'.

$& stores the actually matched pattern.

$上飘 stores whatever came before the matched session.

$' stores whatever came after the matched session.

所以就是前中后. They will stay around until the next successful pattern match. 使用这些automatic match variable 会让程序变慢.

在 v5.10 或更高版本中, 可以通过添加 /p 标记来使用:

1
${^PREMATCH} ${^MATCH} ${^POSTMATCH}

Precedence

关于两个之间的结合。

只有四个level.

1.parentheses () 
2.quantifier *, + and ?, {n,m}
3.anchors and sequence \A, \Z, \z, ^, $, \b, \B
4.vertical bar |
5.so-called atoms

A Pattern Test Program

1
2
3
4
5
6
7
8
while (<>) {    # take one input line at a time
chomp;
if (/YOUR_PATTERN_GOES_HERE/) {
print "Matched: |$上飘<$&>$'|\n"; # the special match vars
} else {
print "No match: |$_|\n";
}
}

Exercise

CHAPTER 9 Processing Text with Regular Expressions

Substitutions with s///

This simply replaces whatever part of a variable matches the pattern with a replacement string.

1
2
3
$_ = "He's out bowing with Barney tonight.";
s/Barney/Fred/;
print "$_\n";

Using Boolean value to judge:

1
2
3
4
$_ = "fred flinnstone";
if (s/fred/wilma/) {
print "Successfully rep;aced fred with wilma."
}

Globale Replacements with /g

like:

1
s/\A\s+|\s+\z//g

Different Delimiters

Using # like:

1
s#\Ahttps://#http://#

使用成对的delimiters需分别括住:

1
s{fred}{barney};

Nondestructive Substitutions

复制一份:

1
2
3
my $original = 'Fred ate 1 rid';
my $copy = $original;
$copy =~ s/\d+ ribs?/10 ribs/;

Adding in Perl 5.14, 使用\rmodifier可保留original strng alone and return a modified copy of it:

1
2
3
use v5.14;

my $copy = $original =~ s/\d+ ribs?/10 ribs/r;

=~ is higher precedence than the =.

Case Shifting

Using \U forces what follows to all uppercase:

1
2
$_ = "I saw Barney with Fred,";
s/(fred|barney)/\U$1/gi;

The \L escape forces lowercase.

Turning off case shifting with \E.

When written in lowercase (\l and \u), they affect only the next character,

Using \u with \L means “all lowercase, but capitalize the first letter.”.

These escape sequences are avaliable in any double-quotish string:

1
print "Hello, \L\u$name\E, would you like to play a game?\n";

Functions lc, uc, fc, lcfirst, and ucfirst. like:

1
2
my $start = "Fred";
my $uncapp = lc( $start );

Metaquoting

太多的backslashes会很乱。Using \Q\E.

\Q quote everything after it. 也可以用quotemetafunction:

1
2
3
4
my $profix = quotemeta( $input_pattern );
if ( s/$prefix(Fred)/$1/ ) {
print "Replaced $1\n";
}

The split Operator

It looks like this:

1
my @field = split /separator/, $string;

就是根据指定的分隔符来分割。返回一个list.

一个rule, leading empty fields are always returned, but trailing empty fields are discarded.

if you want the trailing empty field, give splite a third argument of -1.

The dafault for splite is to break up $_ on whitespace:

1
my @fields = split;

The join Function

The join function looks like this:

join 的第一个参数是string.

1
my $result = join $glue, @pieces;

$glue中的内容来连接@pieces中的元素. 返回resulting string.

Using split and join to work together.

1
2
my @values = split /:/, $x;
my $z = join "-", @values;

m// in List Context

When a pattern match m// is used in a list context, the return value is a list of the capture variables created in the match, or an empty list if the match failed.

m// 是默认的, 也就是普通的 //.

返回捕获组。也可用于分离:

1
2
3
my $text = "Fred dropped a 5 ton granite block on Mr.Slate";
my @words = ($text =~ /([a-z]+)/ig);
print "Result: @words\n";

More Powerful Regular Expressions

Nongreedy Quantifier

Adding ? after the quantifier

匹配as few as possible, like:

1
2
3
my $text = '<b>Fred</b> and <b>Barney</b>';
my $match_count = $text =~ s|<b>(.*?)</b>|\U$1|g;
print "$match_count: $text\n";

会匹配到两个而不是一个.

Fancier Word Boundaries

Adding in Perl 5.22, adding curly braces to denote:

1
2
3
4
5
use v5.22;

my $string = "this doesn't capitalize correctly.";
$string =~ s/\b{wb}(\w)/\U$1/g;
print "$string\n";

\b{wb} 用于单词.
\b{sb} 用于句号.
\b{lb} knows where to insert the newlines.

Matching Multiple-Line Text

Using ^ and $ for whole string.

Adding \m for one line.

Updating Many Files

Perl’s own localtime function.

The special variable $^I. By default it’s undef, when there’s a string in $^I, that string is used as a backup filename’s extension. 即该字符串就会变成备份文件的扩展名,新的内容会output到以原来的文件名为名的文件中.

只要设置了这个变量就会起作用. 会将原来的文件加上这个扩展名.

Perl没有实际修改一个文件, 它创建了一个新文件并传入更新的内容.

1
2
3
4
5
6
7
8
9
10
11
12
13
#! /usr/bin/perl -w

use strict;

chomp(my $date = `date`);
$^I = ".bak";

while (<>) {
s/\AAuthor:.*/Author: Randal L. Schwartz/;
s/\APhone:.*\n//;
s/\ADate:.*/Date: $date/;
print;
}

In-Place Editing from the

命令行参数:

Some options:

1
2
3
4
5
6
7
8
9
10
11
12
-p    Telling Perl to write a program for you like:
while (<>) {
print ;
}

-n Leaving out the automatic print statement.

-i.bak To get a backupfile, if you don't, using -i alone.

-w Turning on the warnings.

-e says "executable code follows" 代码会添加到print之前.

for example:

1
$ perl -p -i.bak -w -e 's/Randall/Randal/g' fred*.dat

Exercise

打开文件,需要判断是否成功打开。

CHAPTER 10 More Control Structures

The unless Control Structure

Executing a block of code only whe n the conditional is false:

1
2
3
unless ($fred =~ /\A[A-Z_]\w*\z/i) {
print "The value of ...";
}

The else Clause with unless

The syntax is:

1
2
3
4
5
unless () {
...
} else {
...
}

The until Control Structure

Revers the condition of a while loop.

1
2
3
until ($j > $i) {
$j *= 2;
}

Statement Modifiers

1
print "$n is a negative number.\n" if $n < 0; 

Perl 还是会evaluated first.

1
2
3
4
&error("Invalid input") unless &valid($input);
$i *= 2 until $i > $j;
print " ", ($n += 2) while $n < 10;
&greet($_) foreach @person;

The Naked Block Control Structure

The so-called “naked” block is one without a keyword or condition.

like:

1
2
3
4
5
6
{
print "Please enter a number: ";
chomp(my $n = <STDIN>);
my $root = sqrt $n;
print "The square root of $n is $root.\n";
}

不是loop,只运行一次.

The elsif Clause

1
2
3
4
5
if ( ! defined $dino ) {
print "The value is undef.\n";
} elsif ($dino =~ /^-?\d+\.?$/) {
print "The value is an integer.\n";
}

Autoincrement and Autodecrement

The autoincrement operator ++.

The autodecrement operator --.

The for Control Structure

like C:

1
2
3
for (initialization; test; increment) {
body;
}

The Secret Connection Between foreach and for

Inside the Perl parser, the keyword foreach is exactly equivalent tp the keyword for.

If it find semicolons, it’s the C-style for.

The last Operator

Like break operator in C.

The last operator immediately ends execution of the loop.

The next Operator

Like continue operator in C.

开始下一轮循环.

The redo Operator

重新开始循环.

Labeled Block

Lables in Perl are like other identifiers.

Recommending to make them to be all uppercase.

To lable a loop block, just put the label and a colon in front of the loop:

1
2
3
4
5
6
LINE: while (<>) {
foreach (split) {
last LINE if /__END__/;
...
}
}

You may use lable after lasr, next, or redo.

The Conditional Operator

C语言有的东西,Perl基本上也有.

The conditional operator: ?:.

1
expression ? if_true_expr : if_false_expr

Logical Operators

Logical AND operator && and logical OR operator ||.

The Value of a Short-Circuit Operator

Using logical OR operator to selecting a default value:

1
my $last_name = $last_name{$someone} || '(No last name)';

The defined-or Operator

The defined-or operator //. Adding in perl 5.10:

1
2
3
use v5.10;

my $last_name = $last_name{$someone} // '(No last name)';

Set a value when there isn’t one already:

1
2
3
4
5
use v5.10;
use warning;

my $name;
printf "%s", $name // '';

Control Structure Using Partial-Evaluation Operators

&&, ||, //, and ?: are share a peculiar property: depending upon the value on the left side, they may or may not evaluate an expression. They are sometimes called partial-evaluation operator.

Exercise

使用say函数要添加use v5.10.

CHAPTER 11 Perl Modules

Finding Modules

Modules come in two types:

  1. Come with Perl
  2. Get from CAPN
  3. Vendor modules

Check if it is already installed

One way, read with perldoc:

1
$ perldoc Digest::SHA

Give details on a module

Using capn command:

1
$ cpan -D Digest::SHA

Install Modules

If you use ExtUtils::MakeMaker.

1
2
$ perl Makefile.PL
$ make install

Special another directory with an INSTALL_BASE argument to Makefile.PL:

1
$ perl Makefile.PL INSTALL_BASE=/Users/fred/lib

Using another module Module::Build

1
2
$ perl Build.PL
$ ./Build install

Specify an alternate installation directory:

1
$ perl Build.PL --install_base=/Users/fred/lib

The .pm file extension stands for “Perl Module”.

启用perl自己的shell来满足依赖:

1
$ perl -MCPAN -e shell

使用cpan来下载, 后面跟模块:

1
$ cpan Module::CoreList LWP CGI::Prototype

使用 Perl Package Manager (PPM) 来下载:

1
$ ppm time::Moment

使用cpanm(for cpanminus):

1
$ cpanm DBI WWW::Mechanize

Using Your Own Directories

Using local::lib.

To see what they set by loading the module on the command line:

1
$ perl -Mlocal::lib

第十一章 Perl 模块 (重读)

绝大部分 Perl 5 代码都是以模块的形式存在的.

寻找模块

Perl 模块有两种来源:

  • 随 Perl 发行版本一同打包
  • 从 CPAN 下载

可使用 perldoc 命令打开模块的文档.

Perl 自带的 cpan 命令可以创建 autobundle 文件. 其会列出所有已经安装了的模块, 包括版本号:

1
$ cpan -a

安装模块

具体查看模块包的 README 文件或 INSTALL 文件.

Perl 的一个自带的模块 ExtUtils:MakeMaker 其提供的一系列工具可以帮忙生成模块安装文件.

使用 MakeMaker 封装:

1
2
$ perl Makefile.PL
$ make install

可指定安装目录:

1
$ perl MakeMaker.PL INSTALL_BASE=/Users/fred/lib

有些模块的工作依赖于其他模块, 所以必须先安装好这些前置模块, 才能继续编译安装.

扩展名 .pm 表示 Perl Module.

安装模块, 可以用 cpan + 包名 如:

1
$ cpan Module::CoreList

安装到自己的目录

CPAN 工具默认会把模块装到与 perl 解释器相同的目录.

使用 local::lib 模块, 可以将新模块安装到自己的用户目录下.

列出 local::lib 模块所改动的所有环境变量设定:

1
$ perl -Mlocal::lib

使用 -I 参数, 就可以根据 local::lib 修改的环境变量来安装模块:

1
$ cpan -I Set::Crossproduct

可以在 CPAN.pm 配置中设定一些参数.

可以用:

1
use local::lib

来告诉程序在哪里找模块.

使用简易模块

Unix 上的文件或目录名称可能会包含换行符.

\s 选项可以修正 . 无法匹配换行符的问题.

File::Basename 模块

使用:

1
$ perldoc File::Basename

可以查看文档.

加载模块:

1
use File::Basename

其提供的 basename 函数用于获取文件的 basename.

dirname 函数用于获取目录名称.

仅选用模块中的部分函数

需要加上一个导入列表:

1
use File::Basename qw/ basename /;

写成:

1
use File::Basename qw/ /;

或:

1
use File::Basename ();

表示在加载模块的同时不导入函数名称, 意思就是只能用全称的方式来引用, 如:

1
my $name = File::Basename::dirname $name;

File::Spec 模块

File::Spec 是 file specification.

使用 catfile 这个 method, 方法的调用需要使用全名:

1
2
3
use File::Spec

my $new_name = File::Spec->catfiel($dirname, $basename);

这里模块称为类 (class), 且使用 ->.

Path::Class 模块

见书或者 perldoc.

CGI.pm 模块

创建 CGI 程序时, 最好直接使用 CGI.pm 模块.

在导入列表中使用 :all (一种导出标签 export tag 写法), 导出一组函数.

1
2
3
4
5
6
7
8
9
#! /usr/bin/perl

use CGI qw(kall);

print header("text/plain");

foreach $param ( param() ) {
print "$param: " . param($param) . "\n";
}

数据库和 DBI 模块

DBI (Database Interface, 数据库接口) 模块. 具体查看 <> 这本书.

DBI 模块的 官网

安装了 DBI 之后, 还需要安装 DBD (Database Driver, 数据库驱动程序). 可在 CPAN 上搜索 DBD.

处理日期和时间的模块

使用 DateTime 模块.

更简便的是使用 Time::Piece 模块, 其会重载 Perl 内置的 localtime 函数, 返回一个时间对象:

1
2
3
4
use Time::Piece

my $t = localtime;
print 'The month is ' . $t->month . "\n";

从 Perl 5.10 起, Time::Piece 模块为 Perl 自带.

第十二章 文件测试

文件测试操作符号

绝大多数测试操作符返回布尔真假值.

它们相应的文档写在 perlfunc 里面.

查看完整清单:

1
$ perldoc -f -X

这里就是 X.

-e 判断是否存在.

-M 判断更新.

列表:

Unix 文件系统上有且仅有 7 种文件类型. 可分别由以下 7 种文件测试操作符代表: -f, -d, -l, -S, -p, -b, -c.

文件时间测试符, -M, -A, -C 分别返回从该文件最后一次被修改, 被访问或者它的 inode 被更改后到现在的天数. 具体细节可查看系统函数 stat 的文档.

天数的值用浮点数表示.

检查文件的时间记录时, 可能会得到 -1.2 这样的负数, 这表示文集那最后一次被访问的时间戳是在未来 30 小时后. 程序开始运行的那一刻会被记录下来作为检查时间的原点. 因此负值可能表示已经运行很久的程序找到某个刚刚才被访问过得文件.

-T-B 测试某个文件是文本文件还是二进制文件. 如果文件不存在, 都为假. 文件为空都为真.

-t, 如果被测试的文件句柄是一个 TTY 设备, 返回真.

如果文件测试操作符后面没写文件名或文件句柄, 那么默认的操作数就是 $_ 里的文件名.

可以有这样的写法:

1
my $size_in_k = (-s) / 1024; 

测试同一文件的多项属性

结合 and:

1
2
3
if (-r $file and -w $file) {
...
}

这种写法比较消耗资源.

使用虚拟文件句柄 _, 其告诉 Perl 使用上次查询过的文件信息来做当前测试:

1
2
3
if (-r $file -w _) {
...
}

或分开写:

1
2
3
4
5
6
if (-r $file) {
print "The file is readable!\n";
}
if (-w _) {
print "The file is writable!\n";
}

栈式文件测试操作符

在 Perl 5.10 之后, 可以这样测试:

1
2
3
4
5
use 5.010;

if (-w -r $file) {
print "The file is both readable and writable!\n"
}

不用分开写, 靠近文件名的测试会先执行, 次序为从右往左, 这种叫做 栈式写法.

stat 和 lstat 函数

stat 函数能返回和同名的 Unix 系统调用 stat 近乎一样丰富的文件信息.

stat 函数的参数可以是文件句柄, 或是某个会返回文件名的表达式.

stat 函数执行失败, 会返回空列表, 或是一个含 13 个元素的数字列表. 具体含义见书.

1
my($dev, $ino, $mode, $nlink, $uid, $gid, $rdev, $size, $atime, $mtime, $ctime, $blksize, $blocks) = stat($filename);

localtime 函数

在标量上下文中:

1
2
my $timestamp = 1180630098;
my $date = localtime $timestamp;

在列表上下文中返回列表:

1
my($sec, $min, $day, $mon, $year, $wday, $yday, $isdest) = localtime $timestamp;

gmtime 函数返回世界标准时间.

默认情况下, localtime 函数和 gmtime 函数都使用当前 time 返回的时间值.

按位运算操作符

如对 stat 函数返回的权限位进行处理.

基本和 C 语言相同.

如下:

使用位字符串

假如按位运算操作符的任一操作数是字符串, 则 Perl 会把它当成位字符串来处理.

如, “\xAA”|”\x55” 的结果会是 “\xFF”.

了解利用按位运算操作符处理位字符串的细节, 参阅 perlop 文档.

第13章 目录操作

在目录树中移动

使用 chdir 操作符来改变当前的工作目录. 和 Unix shell 的 cd 命令差不多:

1
chdir '/etc' or die "cannot chdir to /etc: $!";

发生错误时会设置标量变量 $!.

由 Perl 程序启动的所有进程都会继承 Perl 程序的工作目录.

工作目录的更改无法影响调用 Perl 程序的进程. 意思是, 你用 shell 调用 Perl 程序, 尽管这个 Perl 程序改变了工作目录, 但是 Perl 程序退出后, 又会回到原来的工作目录.

可以用 File::HomeDir 模块, 指定进入用户的主目录.

文件名通配

使用 glob 操作符:

1
2
my @all_files = glob '*';
my @pm_files = glob '*.pm';

其中 @all_files 会取得当前目录中的所有文件并按字母顺序排序, 不包括以点号开头的文件.

所有能在命令行上键入的模式都可以作为 (唯一的) 参数交给 glob 处理:

1
my @all_files_includeing_dot = glob '.* *'

在 Perl 5.6 之前, glob 操作符只是在后台调用 /bin/csh 来展开文件名.

文件名通配的另一种语法

在出现 glob 操作符之前的写法:

1
2
3
my @all_files = <*>;
my $dir = '/etc';
my @dir_files = <$dir/* $dir/.*>;

目录句柄 (directory handle)

和文件句柄使用起来没多大差别.

opendir 打开, 用 readdir 读取, 用 closedir 关闭. 操作的是目录里的文件名, 而不是文件内容.

目录句柄会在程序结束时自动关闭.

readdir 操作符返回的文件名并不含路径名, 只是目录里的文件名. 只有加上路径名称才有办法得到文件的全名.

1
2
3
4
5
6
7
opendir my $somedir, $dirname or die "Cannot open $dirname: $!";
while (my $name = readdir $somedir) {
next if $name =~ /^\./;
$name = "$dirname/$name";
next unless -f $name and -r $name;
...
}

可以使用 File::Spec::FunctionsPath::Class 模块构造用于本地系统的合适文件名.

递归访问目录

Perl 的 File::FindFile::Find::RuleFile::Finder 模块可以实现 Unix 下的 find 命令.

文件和目录的操作

删除文件

使用 unlink 操作符, 并指定要删除的文件列表.

1
2
unlink 'slate', 'bedrock', 'lava';
unlink qw(slate bedrock lava);

可以配合 glob 使用:

1
unlink glob '*.o';

unlink 的返回值代表成功删除的文件数目.

可以使用循环:

1
2
3
foreach my $file (qw(slate bedrock lava)) {
unlink $file or warn "Failed on $file: $!\n";
}

unlink 不能用来删除目录. 删除目录要使用 rmdir.

删除文件的权限跟文件本身的权限位无关, 其取决于文件所在目录的权限位.

重命名文件

使用 rename 函数. 相当与命令行下的 mv.

如:

1
rename 'old', 'new';

可以移到不同目录:

1
rename 'over_there/some/place/some_file', 'some_file';

可以不用逗号而用 =>:

1
rename 'over_there/some/place/some_file' => 'some_file';

批量把名称以 .old 结尾的文件改名为 .new 结尾:

1
2
3
4
5
6
7
8
9
10
11
foreach my $file (glob "*.old") {
my $newfile = $file;
$newfile =~ s/\.old$/.new/;
if (-e $newfile) {
warn "can't rename $file to $newfile: $newfile exists\n";
} elsif (rename $file => $newfile) {

} else {
warn "rename $file to $newfile failed: $!\n";
}
}

链接与文件

目录是一种由系统管理的特殊文件, 基本上目录是一份文件名和相应 inode 编号的对照表. ls -i 可查看 inode.

判断一个 inode 是否可用, 利用 inode 的链接数 (link count). 如果 inode 并未在任何目录里出现, 它的链接数就一定是零.

所有链接数为零的 inode 都可以用来存放新文件.

任何目录的链接数都至少是 2 :一个位于它的上层目录的列表里, 另一个位于它本身的列表里.

利用 Perl 中的 link 函数建立新的链接:

1
link 'chicken', 'egg' or warn "can't link chicken to egg: $!";

和命令行下的 ln chicken egg 效果类似. 这是硬链接. 如果删除了 chichen 文件, 文件里的数据并不会丢失, 还可以用 egg 这个文件名来访问.

目录列表的硬链接还有一条规定: 在目录列表中所有 inode 指向的文件都必须在同一个挂载卷中.

rename 虽然可以将文件移到别的目录里, 但是来源和目的地必须位于同一个文件系统 (挂载卷) 上.

软链接 (symbolic link):

1
symlink 'dodgson', 'carroll' or warn "can't symlink dodgson to carroll: $!";

符号链接是创建了新的 inode.

取得符号链接指向的位置, 使用 readlink 函数:

1
2
3
my $where = readlink 'carroll';

my $perl = readlink '/usr/local/bin/perl';

两种链接都可以用 unlink 移除.

创建和删除目录

使用 mkdir 目录创建:

1
mkdir 'fred', 0755 or warn "Cannot make fred directory: $!";

注意这里用的是八进制数.

1
2
3
my $name = 'fred';
my $permissions = "0755";
mkdir $name, oct($permissions) or die "Cannot create $name: $!";

移除空目录直接用 rmdir, 每次调用只能删除一个目录:

1
2
3
foreach my $dir qw(fred barbey betty) {
rmdir $dir or warn "Cannot rmdir $dir: $!\n";
}

删除非空目录之前, 先用 unlink 删除目录中的内容.

进程号, 存在 $$ 中.

可以参考 File::Path 模块中的 rmtree 函数.

修改权限

使用 perl 中的 chmod 函数:

1
chmod 0755, 'fred', 'barney';

chmod 会返回成功更改的条目数量.

安装 File::chmod 模块可以支持符号表示的权限值.

修改隶属关系

需要使用数字形式的用户标识符及组标识符:

1
2
3
my $user = 1004;
my $group = 100;
chown $user, $group, glob '*.o';

可以使用 getpwnam 函数将用户名转换成用户编号, getgrnam 把用户组名转换成组编号:

1
2
3
defined(my $user = getpwnam 'merlyn') or die 'bad user';
defined(my $group = getgrnam 'users') or die 'bad group';
chown $user, $group, glob '/home/merlyn/*';

chown 会返回受影响的文件数量.

修改时间戳

使用 utime 函数, 前两个参数是新的访问时间和更改时间, 其余参数是要修改时间戳的文件名列表. 时间格式采用的是内部时间戳的格式 (stat 和 lstat 函数).

如:

1
2
3
my $now = time;
my $ago = $now - 24 * 60 * 60;
utime $now, $ago, glob '*';

第十四章 字符串与排序

用 index 查找子字符串

找出子字符串在主字符串中的相对位置:

1
$where = index($big, $small);

返回值是整数, 表示首次出现的第一个字符的位置. 从零算起. 无法找到, 就会返回 1.

1
2
my $stuff = "Howdy world!";
my $where = index($stuff, "wor");

返回 6.

可指定开始查找的位置 (第三个参数):

1
2
3
my $stuff = "Howdy world!";
my $where = index($stuff, "wor", 2);

rindex 返回最后出现的位置.

1
my $last_slash = rindex("/etc/passwd", "/");

结果为四. 其第三个参数用来限定返回值的上限.

用 substr 操作子字符串

如:

1
my $part = substr($string, $initial_position, $length);

可不用指定长度, 直接取到结尾如:

1
my $pebble = substr "Fred J. Flinstone", 13;

起始位置为负数表示倒数.

index 配合使用:

1
2
my $long = "some very very long string";
my $right = substr($long, index($long, "l"));

修改字符串被选取的部分:

1
2
my $string = "Hello, world";
substr($string, 0, 5) = "Goodbye";

变量 string 的前五个字符被替换为后面的字符串, 即 “Goodbye”. (也就是前五个字符被选取出来进行处理).

等同于:

1
my $previous_value = substr($string, 0, 5, "Goodbye");

配合 =~ 和正则表达式:

1
substr($string, -20) =~ s/fred/barney/g;

用 sprintf 格式化字符串

其返回格式化之后的字符串而不是打印.

1
my $date_tag = sprintf "%4d/%02d/%02d %2d:%02d:%02d", $ye, $mo, $da, $h, $m, $s;

格式化定义中数字字段的前置零表示必要时会在数字前补零以符合指定的宽度.

用 sprintf 格式化金额数字

使用 %.2f 这种限定小数位.

有用的程序:

1
2
3
4
5
6
sub big_money {
my $number = sprintf "%.2f", shift @_;
1 while $number =~ s/^(-?\d+)(\d\d\d)/$1,$2/;
$number =~ s/^(-?)/$1\$/;
$number;
}

看不懂就翻书.

非十进制数字字符串的转换

使用 hex()oct 进行转换.

0b 开头表示二进制. 0x 开头表示十六进制, 其他的表示为八进制.

高级排序

Perl 允许你建立自己的 “排序规则子程序 (sort-definition subroutine)”.

排序子程序并不需要排序许多元素, 只要能比较两个元素, Perl 就有办法 (通过不断咨询排序子程序) 返回排好序的数据.

示例:

1
2
3
4
5
sub by_number {
if ($a < $b) { -1 } elsif ($a > $b) { 1 } else { 0 }
}

my @result = sort by_number @some_numbers;

使用排序用的子程序, 不用加 &, 放在 sort 关键词和待排序列表之间.

排序子程序满足的条件是:

  • 如果 $a 排在 $b 之前, 返回 -1
  • 如果 $b 排在 $a 之前, 返回 1
  • 无所谓先后返回 0

记忆方法, $a < $b, 可以看成 -1 0 1, $a 排在前面就是左边的 -1, $b 排在前面就是右边的 1.

许多排序子程序的名称都是以 by_ 开头的.

在排序子程序中并不需要去设定 $a$b, 交给 Perl 去完成就好.

使用飞船操作符 <=> 会更加简洁, 这个操作符会比较两个数字并返回 -1, 0, 或 1.

1
sub by_number { $a <=> $b }

飞船操作符只能用来比较数字.

使用 cmp 来比较字符串. (返回三种比较结果) cmp 所提供的排序与 sort 默认的排序规则相同. (ASCII 码)

如:

1
2
3
sub by_code_point { $a cmp $b }

my @string = sort by_code_point @any_strings;

不区分大小写的排序:

1
sub case_insensitive { "\L$a" cmp "\L$b" }

一般来说, 对 Unicode 字符串排序, 都会写成:

1
2
3
4
5
use Unicode::Normalize

sub equivalents { NFKD($a) cmp NFKD($b) }

my @string = sort equivalents @any_strings;

出于性能的考虑, $a$b 并非数据项拷贝, 实际上它们只是原始列表元素的临时别名. 在中途改变它们的值, 会弄乱原始数据.

更简单的内嵌排序子程序:

1
my @numbers = sort { $a <=> $b} @some_numbers; 

以递减的顺序进行排序, 使用 reverse 函数:

1
my @numbers = reverse { $a <=> $b} @some_numbers; 

按哈希值排序

1
2
3
4
5
my %score = ("barney" => 195, "fred" => 205, "dino" => 30);

my @winners = sort by_score keys %score;

sub by_score { $score{$b} <=> $score{a} }

按多个键排序

上面的类型, 但是有分数相同情况.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
my %score = (
"barney" => 195,
"fred" => 205,
"dino" => 30,
"bamm-bamm" => 30,
);

my @winners = sort by_score_and_name keys %score;

sub by_score_and_name {
$score{$b} <=> $score{$a}
or
$a cmp $b
}

排序的级数可以不止两级, 书中的示例五级代码:

1
2
3
4
5
6
7
@patron_IDs = sort {
&fines($b) <=> &fines($1) or
$items{$b} <=> $items{$b} or
$family_name{$a} cmp $family_name{$b} or
$personal_name{$a} cmp $family_name{$b} or
$a <=> $b
} @patron_IDs;

第十五章 智能匹配与 given-when 结构

智能匹配是从 Perl 5.10.0 开始出现的.

智能匹配操作符

智能匹配操作符 ~~ 会根据两边的操作数的数据类型自动判断改用何种方式进行比较或匹配. 有时, 甚至能取代绑定操作符 =~

如在哈系 %name 中查找任何匹配 Fred 的键:

1
2
3
use 5.0100001;

say "I found a key matching 'Fred'" if %names ~~ /Fred/;

可参考 perlsyn 文档中关于智能匹配的表. (Smart matching in detail)

智能匹配操作的优先级

当使用智能匹配操作符时, Perl 会按表自上而下查看适用的操作书配对, 先找到哪一种搭配就选择哪一种操作.

given 语句

given-when 控制结构能够根据 given 后面的参数执行某个条件对应的语句块. 和 C 中的 switch 语句对应.

隐式写法:

1
2
3
4
5
6
7
8
use 5.0100001

given ( $ARGV[0] ) {
when ( 'Fred' ) { say 'Name is Fred' }
when ( /fred/i ) { say 'Name has fred in it' }
when ( /\AFred/ ) { say 'Name starts with Fred' }
default { say "I don't see a Fred" }
}

显式写法:

1
2
3
4
5
6
7
8
use 5.0100001

given ( $ARGV[0] ) {
when ( $_ ~~ 'Fred' ) { say 'Name is Fred'; break }
when ( $_ ~~ /fred/i ) { say 'Name has fred in it'; break }
when ( $_ ~~ /\AFred/ ) { say 'Name starts with Fred'; break }
default { say "I don't see a Fred"; break }
}

可以看到有智能匹配和 break.

given-when 可以在满足某个条件的基础上继续测试其他条件, 在 when 语句块的末尾使用 continue, Perl 就会尝试执行后续的 when 语句.

笨拙匹配

指自己明确书写操作符:

1
2
3
4
5
6
7
8
use 5.0100001;

given ( $ARGV[0] ) {
when ( $_ eq 'Fred') { say 'Name is Fred'; continue }
when ( $_ =~ /\AFred/ ) { say 'Name starts with Fred'; continue }
when ( $_ =~ /fred/i ) { say 'Name has fred in it'; }
default { say 'Name is Fred' }
}

也可以智能匹配和笨拙匹配混用.

智能匹配操作符用于判断事物是否相同 (或者差不多是相同), 所以在需要比较大小时, 就不能用智能匹配.

否定的表达式, 包括否定的正则表达式, 都不会使用智能匹配方式.

多个条目的 when 匹配

要遍历多个元素, 可以直接省略 given, 让 foreach 将当前正在遍历的元素放入它自己的 $_ 里:

1
2
3
4
5
6
7
8
use 5.0100001;

foreach ( @name ) {
when ( /fred/i ) { say 'Name has fred in it'; continue }
when ( /\AFred/ ) { say 'Name starts with Fred'; continue }
when ( /fred/i ) { say 'Name has fred in it'; }
default { say 'Name is Fred' }
}

第十六章 进程管理

system 函数

用于启动子进程.

调用 Unix 的 date 命令:

1
system 'date';

此时的 Perl 程序称为父进程 (parent). 运行时, system 命令根据当前的父进程创建一份拷贝, 这份拷贝称为子进程 (child), 其继承了原来进程中 Perl 的标准输入, 标准输出以及标准错误.

通常提供给 system 函数的参数就是那些一般在 shell 中键入的命令.

如:

1
system 'ls -l $HOME'

这里用的单引号, 否则会被认为是 Perl 中的标量符号.

当命令简单时, 不会调用 /bin/sh 这种 shell.

避免使用 Shell

使用多个参数来调用:

1
2
3
my $tarfile = 'something*wicked.tar';
my @dirs = qw(fred|flintstone <barney&rubble> betty );
system 'tar', 'cvf', $tarfile, @dirs;

system 操作符的返回值是根据子进程的结束状态来决定的.

环境变量

Perl 会在需要时用 PATH 环境变量来搜索程序以便运行.

在 Perl 中, 环境变量可通过特殊的 %ENV 哈希取得, 其中每个键都代表一个环境变量. 在程序开始运行时, %ENV 会保留从父进程 (通常为 shell) 继承而来的设定值, 修改次哈希就能改变环境变量, 它会被新进程继承.

如:

1
$ENV{'PATH'} = "/home/rootbeer/bin:$ENV{'PATH'}";

exec 函数

所有适用于 system 函数的所有语法也都适用于 exec 函数.

区别: system 函数会创建子进程, 子进程会在 Perl 睡眠期间执行任务. exec 函数却导致 Perl 进程自己去执行任务.

如:

1
2
chdir '/tmp' or die "Cannot chdir /tmp: $!";
exec 'bedrock', '-o', 'args1', @ARGV;

当运行到 exec 时, Perl 找到 bedrock 并且 “跳进去” 执行, 此后, 就没有 Perl 进程了, 只有那个运行 bedrock 命令的进程, 这样在 bedrock 运行结束时, 没有 Perl 进程在等待. (也就是说, Perl 创建了一个子进程, 然后被这个子进程替换掉)

exec 调用之后写的代码都无法运行. 不过如果启动过程出现错误, 那么后续的捕获语句还是可以继续运行的:

1
2
exec 'date';
die "date couldn't run: $!";

用反引号捕获输出结果

无论是 system 还是 exec, 所执行命令的输出都会送往 Perl 的标准输出.

可以用 “``“ 来捕获输出.

1
2
my $now = `date`;
print "The time is now $now";

注意这里没有自动去除换行符. 反引号会以双引号内的字符串的方式解释, 即可以使用 Perl 中的变量.

除反引号外, 还可以使用引起操作符 qx(). 分隔符可换, 如用 qx'' 避免变量内插.

可将标准输入重定向到 /dev/null 来避免输入:

1
my $result = `some_questionable_command arg arg argh </dev/null`;

在列表上下文中使用反引号

如果命令输出很多行, 那么标量上下文中使用反引号会得到一个很长的字符串, 其中包含换行符. 不过, 如果是在列表上下文使用同样的反引号, 则会输出字符串按行拆分的列表.

1
my @who_lines = `who`;

@who_lines 中有多个以换行符结尾的字符串.

用 IPC::System::Simple 执行外部进程

其需要从 CPAN 下载.

可直接使用该模块提供的同名函数取代内置的 system 函数, 但其可移植性更好.

1
2
3
4
5
use IPC::System::Simple qw(system);

my $tarfile = 'something*wicked.tar';
my @dirs = qw(fred|flintstone <barney&rubble betty>);
system 'tar', 'cvf', $tarfile, @dirs;

提供的 systemx 函数在执行外部命令时不会通过 shell 调用.

捕获外部命令的输出, 使用 capturecapturex.

通过文件句柄执行外部的进程

并发式运行子进程, 将命令放在 open 调用道德文件名部分, 并且在它前面或后面加上竖线 (即管道符号), 两个参数的形式如下:

1
2
open DATE, 'date|' or die "cannot pipe from date: $!";
open MAIL, '|mail merlyn' or die "cannot pipe to mail: $!";

| 在右边表示该命令的标准输出连接到 DATE.

| 在左边表示该命令的标准输入连接到 MAIL.

三个参数的形式如下:

1
2
open my $date_fh, '-|', 'date' or die "cannot pipe from date: $!";
open my $mail_fh, '|-', 'mail merlyn' or die "cannot pipe to mail: $!";

- 的位置替代了原来的 command.

也可三个以上的参数:

1
open my $mail_fh, '|-', 'mail', ' merlyn' or die "cannot pipe to mail: $!";

从以读取模式打开的文件句柄中读取数据:

1
my $now = <$date_fh>;

发送数据到 mail 进程:

1
print $mail_fh "The time is now $now";

结束:

1
2
close $mail_fh;
die "mail: non-zero exit of $?" if $?;

退出状态保存在 $? 中.

用 fork 进行深入和复杂工作

1
system 'date';

这条语句的低级实现:

1
2
3
4
5
6
defined(my $pid = fork) or die "Cannot fork: $!";
unless ($pid) {
exec `date`;
die "cannot exec date: $!";
}
waitpid($pid, 0);

fork 调用成功会返回一个 pid, 失败返回 undef.

waitpid 用于等待特定的子进程结束.

深入了解可以查看 perlipc 文档.

发送及接收信号

发送信号

在 Linux 中可以用 kill -l 列出所有的信号.

在 Perl 中使用 kill 发送信号:

1
kill 2, 4200 or die "Cannot signal 4021 with SIGINT: $!";

这里的 2 是信号, 也可以用名称如 INT, 4022 是 pid:

1
kill 'INT', 4200 or die "Cannot signal 4021 with SIGINT: $!";

可以使用 =>, 这样信号名称不用被单引号包裹, 其会自动作为裸字符串:

1
kill INT => 4200 or die "Cannot signal 4021 with SIGINT: $!";

编号为 0 的特殊信号表示, 尝试是否能向这个进程发送信号. 可用于探测进程是否存活:

1
2
3
unless (kill 0, $pid) {
warn "$pid has gone away";
}

接收信号

也就是处理向这个程序发送的信号.

对特殊哈希 %SIG 赋值会启动信号处理程序 (直到撤销为止). 哈希值是子程序名 (这里不加 & 号). 如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
my $temp_directory = "/tmp/myprog.$$";
mkdir $temp_directory, 0700 or die "Cannot create $temp_directory";

sub clean_up {
unlink glob "$temp_directory/*";
rmdir $temp_directory;
}

sub my_int_handler {
&clean_up();
die "interrupted, exiting...\n";
}

$SIG{'INT'} = 'my_int_handler';
.
.
.
&clean_up;

设置标记:

1
2
3
4
5
6
7
8
9
10
11
my $int_count = 0;
sub my_int_handler { $int_count++ };
$SIG{'INT'} = 'my_int_handler';
...;
while (<SOMEFILE) {
...;
if ($int_count) {
print "[processing interrupted...]\n";
last;
}
}

第十七章 高级 Perl 技巧

切片

列表切片, 就是可以用索引取得列表中的值 (把列表当作数组使用):

1
my $mtime = (stat $some_file)[9];

这里的圆括号是必须的.

一次取一个值, 如:

1
2
my $card_num = (split /:/)[1];
my $count = (split /:/)[5];

一次取两个值, 如:

1
my($card_num, $count) = (split /:/)[1, 5];

可以用 -1 代表最后一个元素.

1
my @numbers = (@name)[9, 0, 1];

数组切片 (array slice)

从数组中切出元素时不需要圆括号, 上面改为:

1
my @numbers = @name[9, 0, 1];

Perl 中 $ 符号意味着一个东西, 而 @ 符号意味着一组东西.

以 Perl 的习惯来看开头的符号和结尾的方括号, 方括号意味着你要检索数组成员, @ 符号意味着获取整个列表, $ 符号意味着取单个元素.

如果前面有个 $ 符号, 下标表达式就会在标量上下文中求得索引值, 但如果之前有个 @ 符号的话, 下表表达式就会在列表上下文中计算

切片可以直接内插到字符串中.

列表的元素会被 Perl 内置的 $" 变量的内容填充, 而它的默认值就是空格, 一般不应改变. 内插时, Perl 实际上会执行 join $",@list.

修改数组中的元素:

1
2
3
my $new_home_phone = "555-6099";
my $new_address = "99380 Red Rock West";
@items[2, 3] = ($new_address, $new_home_phone);

哈希切片 (hash slice)

切片一定是列表, 因此哈希切片也是用 @ 符号来表示.

在 Perl 中看到类似 @score{...} 的写法, 用 Perl 的习惯来看就是, 花括号意味着你要检索哈希成员, @ 符号意味着获取的是整个列表.

1
2
my @three_scores = ($score{"barney"}, $score{"fred"}, $score{"dino"});
my @three_scores = @score{ qw/barney fred dino/ };

百分号符号表示的是整个哈希, 哈希切片一定是列表而不一定是整个哈希.

捕获错误

可参阅 <>

使用 eval

把代码包裹在 eval 块中.

eval 块只是一个表达式, 要在结尾加 ;

1
eval { $barney = $fred / $dino };

只要 eval 发现在它的监察范围内出现致命错误, 就会立即停止运行整个块, 退出后继续运行其余代码.

和子程序相同, eval 的返回值是语句块中最后一条表达式的执行结果.

如果 eval 捕获到了错误, 那么整个语句块将返回 undef. 并且在特殊变量 $@ 中设置错误消息.

1
2
3
use 5.010;
my $barney - eval { $fred / $dino } // 'NaN';
print "I couldn't divide by \$dino: $@" if $@;

可以构造一个有返回值的代码块:

1
2
3
unless ( eval { some_sub(); 1 } ) {
print "I couldn't divide by \$dino: $@" if $@;
}

在列表上下文中, 捕获到错误的 eval 会返回空列表.

eval 块可以设定 my 变量的新作用域, 块内的语句数目不限:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
foreach my $person (qw/ fred wilma betty barney dino pebbles /) {
eval {
open my $fh, '<', $person
or die "Can't open file '$person': $!";

my($total, $count);

while (<$fh>) {
$total += $_;
$count++;
}

my $average = $total / $count;
print "Average for file $person was $average\n";

$do_something($person, $average);
};

if ($@) {
print "An error occurred ($@), continuing\n";
}
}

可以嵌套使用 eval, 内层的 eval 通过 die 向外层报告.

有 4 中类型的错误是 eval 无法捕获的:

  • 语法错误, 如忘写分号 ; (perl 解释器的编译器会在解析源代码时捕获这类错误并在运行程序前停下来, 而 eval 仅仅能捕获 Perl 运行时出现的错误)
  • 让 perl 解释器本身崩溃的错误, 如内存溢出 (其会让 perl 解释器意外终止)
  • 无法捕获警告
  • exit 退出

更为高级的错误处理

die 抛出异常, 用 eval 捕获异常.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
{
local $@;

eval {
...;
die "An unexpected exception message" if $unexpected;
die "Bad denominator" if $dino;
};

if ($@ =~ /unexpected/) {
...;
} elsif ($@ =~ /denominator/) {
...;
}
}

使用 Try::Tiny 模块, 其不是内置模块:

1
2
3
4
5
6
7
8
9
10
11
use Try::Tiny;

try {
...; # 可能会抛出异常的代码
}
catch {
...; # 某些处理异常的代码
}
finally {
...;
}

可省略 catchfinally 块. 只用 try 来忽略错误:

1
my $barney = try {$fred / $dino};

Try::Tiny 把错误消息放到了默认变量 $_.

autodie

1
2
use autodie;
open my $fh, '>', $filename; # 在发生错误时仍会调用 die 函数

autodie 模块默认会对一系列 Perl 内置的用于处理文件, 文件句柄, 进程间通信和套接字的函数自动施行监管, 也可以指定:

1
use autodie qw( open system :socket );

autodie 抛出错误时, 它会把一个 autodie::exception 对象放到 $@ 变量中.

用 grep 筛选列表

如:

1
my @odd_numbers = grep { $_ % 2 } 1..1000;

grep 的第一个参数为一个代码块, 第二个为列表. grep 对列表中的每个元素计算出代码块的值. 代码块中的表达式返回真假值供 grep 判断.

grep 运行中 $_ 会轮流成为列表中每个元素的别名.

从一个文件中取出包含 fred 的行:

1
my @matching_lines = grep /\bfred\b/i, <$fh>;

grep 操作符在标量上下文中返回的是符合过滤条件的元素个数:

1
my $line_count = grep /\bfred\b/i, <$fh>; 

用 map 把列表元素变形

1
2
my @data = (4.75, 1.5, 2, 1234, 6.9456, 12345678.9, 29.95);
my @formatted_data = map { &big_money($_) } @data;

grep 类似, 但其不返回真假值, 而是表达式实际的计算结果, 最终返回一系列这样的结果组成的列表.

事实上, 任何形式的 grepmap 语句都可以该写成 foreach 循环.

可以用简单的表达式和逗号而非一整个语句块:

1
2
print "Some powers of two are:\n", 
map "t" . ( 2 ** $_ ) . "\n", 0..15;

更花哨的列表工具

Perl 中有一些模块专门用于列表数据的处理.

List::Util 模块包含在标准库中, 其用 C 语言实现.

如, 在找到第一个符合条件的元素时结束遍历, 返回结果, 使用 List::Util 提供的 first 子程序:

1
2
use List::Util qw(first);
my $first_match = first { /\bPebbles\b/i } @characters;

计算总和, 利用 sum 子程序:

1
2
use List::Util qw(sum);
my $total = sum( 1..1000 );

最大值:

1
2
use List::Util qw(maxstr);
my $max = maxstr( @strings );

元素随机排序:

1
2
use List::Util qw(shuffle);
my @shuffled = shuffle(1..1000);

List::MoreUtils, 提供更多工具, 但不是 Perl 自带的.


Learning Perl Notes
http://example.com/2022/07/04/Learning-Perl-Notes/
作者
Jie
发布于
2022年7月4日
许可协议