perl入门

Perl语言入门

时间:2015-07-13 18:55来源:网络整理 作者:KKWL 点击:
一、 标量数据 数字 在Perl内部,所有数字按照双精度浮点数来保存和运算。 对于非十进制的整数直接量八进制以0开头(如0377),十六进制以0x开头,二进制以0b开头。对于非常长的数

一、 标量数据

数字
在Perl内部,所有数字按照双精度浮点数来保存和运算。
对于非十进制的整数直接量——八进制以0开头(如0377),十六进制以0x开头,二进制以0b开头。对于非常长的数字(例如 0x00411137和987654321),可以用下划线分割(如0x0041_1137和987_654_321)


字符串
Perl的字符串没有长度限制,而且完全支持Unicode,可以在字符串中使用任意的Unicode字符。不过由于历史原因。它不会自动将源代码当作Unicode编码的文本读入,所以如果想要使用Unicode书写的直接量,必须手工加上utf8编译指令。
字符串有两种书写方式:单引号内的字符串和双引号内的字符串——单引号内所有的字符都代表它们自己,除了部分由反斜线表示的转意字符;双引号中的反斜线更为强大,可以转意很多控制字符。为了更清楚地区分如何使用单引号和双引号,可以在由单个字符组成的简单字符串中使用单引号,其他包括转意、插入变量等情形都使用双引号。

1
2
'barney' #单引号内的简单字符串
"Hello world\n $version \x{2668}" #双引号内的字符串

进行变量内插时,Perl会尽可能使用最长且合法的变量名称,要是你想在内插的值后面紧接着输出字母、数字和下划线,可能会碰上麻烦。同其他shell脚本一样,Perl里我们可以用一对花括号将变量名围起来以避免歧义,或者可以先把字符串拆成两半,再利用连接操作符拼接起来。
常用的字符串操作符有两种——字符串连接(".")和字符串重复("x")

1
2
'hello' . 'world' #等同于"helloworld"
'fred' x 3 #等同于'fredfredfred'

 

标量变量
所谓变量,就是存储一个或多个值得容器的名称。而标量变量,就是只存储一个值得变量,还有其他类型变量,例如数组和哈希,它们都可以存储多个值。
Perl程序里面大部分变量名称都习惯使用小写,如果变量名不止一个单词,可以使用下划线分开($underscores_are_cool)或者驼峰命名法($giveMeInitialCaps)

 

布尔值
Perl没有专用的Boolean数据类型,它是靠一些简单的规则来判断的:

  • 如果是数字,0为假,其他为真

  • 如果是字符串,空字符串为假,其他为真('0'比较特殊,值为假)

  • 如果既不是数字也不是字符串,就先转换成数字或字符串再行判断。

这里还有个小技巧,由于'!'可以颠倒真假值,而Perl有没有专门的布尔类型变量,所以人们常常把布尔值归一化到以下两个值来表示:

1
2
$still_true = !! '1'
$still_false = !! '0'

 

其他常用的规则
惯例一:任何需要变量的地方,都可以用赋值运算表达式代替。实际上Perl会先做复制运算,然后返回复制后的变量。

1
chomp($test = <STDIN>); # 读入文字,略过最后的换行符

惯例二:除非去掉括号会改变表达式的意义,否则括号可以省略。

1
2
$text = <STDIN>;
chomp $text;

惯例三:Perl程序员常常根据需要把新变量当作零或空字符串来用。

1
2
3
4
5
6
# 累加一些奇数
$n = 1;
while ($n < 20) {
    $sum +=$n;
    $n += 2; #准备下一个奇数
}

二、 列表与数组

列表指的是标量的有序集合,而数组则是存储列表的变量;精确的说,列表指的是数据,数组指的是变量。
访问数组中的元素
数组元素是以连续的整数来编号,从0开始,之后的每一个元素依次加1,例如

1
2
3
$fred[0] = "yabba";
$fred[1] = "abba";
$fred[2] = "doo";

数组的名字空间和变量的名字空间是完全分开的,因此可以在同一个程序里再取一个名为$fred的变量而不会冲突。
任何求值能得到数字的表达式都可以作为下标。如果它不是整数,则会自动舍去小数,无论正负。

 

特殊的数组索引
例如,对于上例中的数组@fred,最后一个元素的索引值是$#fred。
不过Perl为此类用法提供的更为简洁的“负数数组索引值”。上例中,$fred[-1]="doo", $fred[-2] = "abba"……

 

列表直接量
列表直接量由圆括号隔开的一串数据表示,这些数据就称为列表元素。例如

1
2
(1, 2, 3) #包含1、2、3这三个数字的列表
(1..100) #100个整数构成的列表

注意,范围操作符".."会从左边的数字计数到右边,每次加1;但是范围操作符只能向上计数,如果需要一个倒序的列表,可以使用reverse 操作符。

在Perl程序里,经常需要建立简单的单词列表,这时只需要使用qw简写,就可以省去键入无谓引号的麻烦,例如:

1
2
3
4
5
6
7
8
9
qw( fred barney betty wilma dino )
或者
qw#   #看起来像注释,不过这里用作定界符,此外还可以用!、/、[]、{}等任何标点符号作为定界符
    fred  #可以使每个元素独立成行,便于阅读和修改
    barney
    betty
    wilma
    dino
#

 

常用的数组操作符
pop和push操作符,pop负责取出数组中的最后一个元素并将其返回,push将一个或多个元素添加到数组的尾端,例如

1
2
3
@array = 5..9;
$fred = pop @array  #$fred变成9,@array变成(5, 6, 7, 8)
push @array, 9 #@array变成(5..9)

shift和unshift操作符同上面的完全相反,处理数组开头的部分,例如

1
2
3
@array = qw # dino fred barney #;
$m = shift(@array); # $m变成"dino", @array变成("fred", "barney")
$n = unshift(@array, "betty"); # @array变成("betty", "fred", "barney")

splice操作符用于操作数组中间的的某些元素,它可以接受至少两个,最多四个参数,例如:

1
2
3
4
5
6
7
8
@array = qw( pebbles dino fred barney betty )
@removed = splice @array, 3 # 删掉元素barney及其以后元素,@removed变成qw ( barney betty )
@array = qw( pebbles dino fred barney betty )
@removed = splice @array, 1, 2 # 从索引1开始,删掉2个元素,@array变成qw( pebbles barney betty ),@removed变成qw( dino fred )
@array = qw( pebbles dino fred barney betty )
@removed = splice @array, 1, 2, qw( wilma ) # @array变成qw( pebbles wilma barney betty ),@removed变成qw( dino fred )

reverse操作符

1
2
@fred = 6..10;
@barney = reverse @fred; # 得 10, 9, 8, 7, 6

必须注意,reverse不会修改传进来的参数,只会返回次序相反的列表。
sort操作符
sort操作符会读取列表的值,而且会根据ASCII码的大小对它们进行排序。

1
2
@fred = 8..12;
@rocks = sorts @fred # 得到10, 11, 12, 8, 9

each操作符
Perl 5.12版引入了each操作符,用于返回数组中下一个元素所对应的索引和值。

1
2
3
4
5
use 5.012;
my @rocks = qw / bedrock slate rubble granite /;
while (my($index, $value) = each @rocks) {
    say "$index: $value";
}

 

foreach控制结构
foreach能逐项遍历列表中的值。控制变量不是列表元素的复制品,它就是列表元素本身。(我想Perl内部应该是用指向列表的指针实现的控制变量)
加入在foreach循环开头省略控制变量,Perl就会用它最喜欢的默认变量"$_",在许多情况下,当未告知Perl使用哪个变量或数值时,Perl都会自动使用"$_"。

标量上下文与列表上下文
如同自然语言,Perl的同一个表达式出现在不同的地方会有不用的意义。

对于数组来说,在列表上下文中,它会返回元素的列表;在标量上下文中,则返回数组中元素的个数:

1
2
3
@people = qw (fred barney betty)
@sorted = sort @people # 列表上下文:barney, betty, fred
$number = 42 + @people # 标量上下文:42 + 3 = 45

当然,并不是一定会在标量上下文中得到列表的个数。比如sort在标量上下文中会返回undef。(实际上没人需要统计列表排序后的元素个数)
reverse是一个更加有趣的例子:

1
2
@backwords = reverse qw/ yabba dabba doo /; #会得到 doo,dabba,yabba
$backwords = reverse qw/ yabba dabba doo /; #会得到oodabbadabbay

 

相对于上面的用法,在列表上下文中使用产生标量的表达式就简单很多了:如果表达式求值结果为标量值,则自动产生一个仅含此标量值的列表。

1
@fred = 6 * 7; # 得到仅有一个元素的列表(42)

 

强制指定标量的上下文
可以使用伪函数"scalar"从列表上下文切换到标量上下文:

1
2
@rocks = qw( talc quartz jade obsidian );
print "I have", scalar @rocks, "rocks!\n"; #打印出的是石头的种数

 

列表上下文中的<STDIN>
在标量上下文中,<STDIN>会返回输入数据的下一行;在列表上下文中,会返回所有剩下的行,直到文件结束为止,返回的每一行都会成为列表中的元素。
当输入数据来自某个文件时,列表上下文会读取文件的剩余部分。如果是键盘输入:Unix或类似系统可以键入"Control+D"来告知系统不会有任何输入了;对于Windows来说,需要使用"Control+Z"。

三、 子程序

同标量和列表一样,子程序也有独立的名字空间,这样Perl就不会将同一段代码中的子程序&fred和标量$fred搞混了。
定义和调用子程序

1
2
3
4
5
sub marine {
    $n += 1; #全局变量 $n
    print "Hello, sailor number $n!\n";
}
&marine(); #调用子程序

如果你定义了两个重名的子程序,那么后面的子程序会覆盖掉前面的那个。

调用子程序时的&号是可以省略的——如果编译器在调用子程序时看到过子程序的定义,或者Perl通过语法规则判断它只能是子程序调用,那么对待该子程序就可以像内置函数那样,调用时省略与号。有一个例外,加入这个子程序与Perl内置函数同名,为了避免歧义,必须使用与号。
所以真正的省略规则如下:除非你知道Perl所有的内置函数名,否则请务必在调用函数时使用与号。

注意:

1. 子程序中所有的变量都是全局的,除非用"my"修饰符声明是私有变量。
2. 所有的子程序都有返回值,即最后一次运算的结果;返回值可以为标量,也可以是列表。

 

参数列表
除了使用全局变量,子程序可以有自己的参数。Perl会自动将参数列表化名为特殊的数组变量"@_",该变量在子程序的私有变量。(加入已经有了全局变量@_,则该变量在子程序调用前会被存起来,并在子程序返回时恢复原本的值)
在真实的Perl代码中,常常把任意长度的列表作为参数传递给子程序。如果传入参数的个数不正确怎么办呢?可以通过检查@_的长度来判断是否正确——但在实际编写Perl程序中这种检查方式很少见,更好的做法是让子程序自动适应任意数目的参数。


关于词法(my)变量
词法变量可使用在任何语句块内,而不仅限于子程序的语句块。比如说,它可以在if、while、或foreach的语句块里使用:词法变量可使用在任何语句块内,而不仅限于子程序的语句块。比如说,它可以在if、while、或foreach的语句块里使用:

1
2
3
4
foreach (1..10) {
    my($square) = $_ * $_;
    print "$_ squared is $square.\n";
}

有经验的程序员都知道(这往往是付出惨痛代价换来的),将变量作用域圈定在一页或少数几行代码内,的确可以加快开发及测试周期。

持久性私有变量
使用state操作符声明的变量,我们可以在子程序多次调用期间保留变量之前的值,并将变量的作用域限于子程序内部。

 

use strict编译指令
Perl是一门相当宽容的编程语言,但也许你希望Perl能更加严格一些。"use strict"编译指令是要告诉Perl内部的编译器接下来的代码应该严谨一些,遵循一些优良的编程风格。
如果你在程序写完后再加"use strict",通常会得到一大堆警告信息;因此如果有需要,最好在开始写程序时就用它。

四、 输入与输出

标准输入输出

STDIN可以用于接收键盘输入或是文件输入。在标量上下文中执行该操作时,会返回输入中的下一行。

1
2
3
4
5
6
7
8
while <STDIN> {
    chomp;
    print "I saw $_";
}
foreach <STDIN> {
    chomp;
    print;
}

看起来上例中的“while”循环和“foreach”的行为完全一样,其实是有些差别的——在while循环里,Per会读取一行输入,把它存入某个变量并且执行循环的主体,接下来它会回去寻找其他输入行;foreach循环中,行输入操作符会在列表上下文中执行(因为foreach需要逐项处理列表内容),因此Perl会将全部内容读入内存,然后才开始执行循环。当需要处理的输入数据长度很大时,比如处理400M大小的Web服务器日志文件,foreach的效率会比while低很多。因此最好的做法是尽量使用while循环,让它每次处理一行。

 

钻石操作符的输入<>

钻石操作符提供类似 标准Unix工具程序的参数输入方式,例如对于如下Perl程序“my_program”:

1
2
3
4
while <> {
    chomp;
    print;
}

如果执行命令“./my_program fred barney betty”,它应该会处理文件fred,接着是barney,最后是betty。(钻石操作符怎么会知道检查命令行参数呢?其实它的参数是来自@ARGV数组)

如果执行命令是没有指定调用参数,程序会从标准输入流采集数据。

当然还有个例外,如果参数中包含连字符“-”,则Perl代码处理到连字符时,会临时改从标准输入读取数据。

 

使用print/printf/say输出到标准输出

Print操作符会读取后续列表中的所有元素,并把每一项一次送到标准输出。

1
2
print <>; #相当于Unix下的cat命令
print sort <>; #相当于Unix下的sort命令

如果对print的输出格式不够满意,还可以使用printf来产生格式化过的输出结果。

1
printf "Hello, %s; your password expires in %d days!\n", $user, $days_to_die;

当然Perl一如既往地提供了更方便的格式"%g" (你可以把"g"当成"General"数字转换)。

一般来说,我们编写printf的格式化字符串时已经确定了替换参数的个数和类型,不过万事没有绝对,下面的例子就是用程序在运行时动态产生格式字符串。

1
2
3
my @items = qw( wilma dino pebbles );
my $format = "The items are: \n" . ("%10s\n" x @items);
printf $format, @items;

另外,有个叫Perl Power Tools (PPT)的项目目标就是用perl重写所有经典Unix工具程序,但是在重写shell的时候陷入了难题。PPT项目一度非常有用,因为它使所有便准的工具程序可以运行在非Unix机器上。

Say函数的功能和print的差不多,但在打印每行内容时都会自动加上换行符。所以下面几种写法的输出结果都一样:

1
2
3
4
use 5.010
print "Hello!\n";
print "Hello!" . "\n";
say "Hello!";

 

文件句柄

一般使用全大写字母来命名文件句柄,但是有6个个数文件句柄是Perl保留的——STDIN、STDOUT、STDERR、DATA、ARGV以及ARGVOUT。

1
2
3
4
open CONFIG, 'dino'; #打开一个文件
open CONFIG, '<dino' #只读方式打开一个文件
open REDROCK, '>fred' #创建一个新的文件,如果已经存在,则清除原有内容并以新内容代替
open LOG, '>>logfile' #追加方式打开一个文件,如果文件不存在,则创建一个新文件

5.6版的Perl里,加入了open的三个参数的写法:

1
2
3
4
5
open CONFIG, '<', 'dono';
open BEDROCK, '>', $file_name;
                                                                         
open CONFG, '<:encoding(UTF-8)', $file_name; #使用UTF-8编码打开文件,这种书写方式会确认编码是否正确
open BEDROCK, '>:utf8', &logfile_name(); #这种简写的方式不会考虑输入输出的数据是否真的是合法的UTF-8字符串

我们可以通过下面的这条命令打印出说有perl能理解的字符编码清单:

1
% perl -MEncode -le "print for Encode->encodings(':all')"

除了字符编码之外,数据输入或输出过程中还可以做其他转换操作。比如DOS风格和Unix风格的换行符:

1
2
open BEDROCK, '>:crlf', $file_name; #按照DOS换行符风格写入文件
open BEDROCK, '<:crlf', $file_name; #读取DOS风格的文件

 

 

关闭文件句柄

1
close BEDROCK;

 

出错处理

当Perl遇到致命错误时,你的程序应该立刻中止运行,并发出错误信息告知原因。这样的功能可以用die函数来实现。

1
2
3
if ( ! open LOG, '>>', 'logfile' ) {
    die "Cannot create logfile: $!"; #die函数会终止程序的运行并打印出错信息
}

"$!"代表可读的系统错误信息。一般来说,当系统拒绝我们的请求时,"$!"会给出一个解释,类似于C语言中调用perror取得的字符串。

如果Perl遇到的错误是非致命的,可以使用warn函数送出警告信息。warn函数的功能就是产生类似于Perl的内置警告信息的信息(比如启用警告信息时,使用某个undef变量却将它当成已有的值来参与计算,就会触发警告信息)。

 

自动检测致命错误

从Perl 5.10开始,为人称道的autodie编译指令已经成为标准库的一部分。

1
use autodie;

这条编译指令是靠判别具体操作的类型来工作的。如果Perl内置函数调用了操作系统接口的话,那么中途出现的错误并不是编程人员所能控制的,所以一旦发现系统调用出错,autodie便会自动帮你调用die。

 

使用文件句柄

以写入或添加方式打开文件后,可以直接使用print或printf将字符串输出到文件中:

1
2
print LOG "Captain's log, stardate 3.14159";
printf STDERR "%d percent complete.\n", $done/$total * 100;

如果print/printf的参数列表没有提供文件句柄,则字符串默认被输出到STDOUT。不过可以使用select操作符改变默认的文件句柄,另外还有一个很奇特的变量"$|",当它的值被设为1,就会使当前的默认句柄在每次输出操作后立刻刷新缓冲区。所以,如果要让输出的内容立即显示(比如在读取监视某个耗时程序的实时日志时),应该这么做:

1
2
3
4
select LOG; # 将默认输出设定为LOG文件句柄
$|= 1; # 不要将LOG的内容保留在缓冲区
select STDOUT;
print LOG "This gets written to the LOG at once!\n";

 

五、 哈希

什么是哈希

哈希是一种数据结构,它和数组的相似之处在于可以容纳任意多的值并能按需取用,它和数组的不同在于索引方式,数组总是以数字来索引,哈希则以名字来索引。哈希的索引值称之为键(Key),它是任意唯一的字符串。

一些典型的应用例如:按名字查找姓、按主机名查找IP、按单词统计其出现的次数、按用户名统计每个人使用的磁盘块数量……

 

访问哈希元素

要访问哈希元素,需要使用如下语法

1
2
3
4
$hash{$some_key}
                                                             
$family_name{ 'fred' } = 'flintstone';
$family_name{ 'barney' } = 'rubble';

哈希有自己的名字命名空间,但是在挑选哈希名的时候,最好使得哈希名和键之间能放进去一个"for"字,比如"family_name for fred 是 flintstone"。

访问哈希表里不存在的值会得到undef。

要指代整个哈希,可以用百分号做为前缀。

1
2
%some_hash = ('foo', 35, 'bar', 12.4, 2.5, 'hello', wilma, 1.72e30, 'betty', 'bye\n'); #直接对哈希表赋值
@any_array = %some_hash #展开哈希,将它变成键-值对列表。

 

常用的哈希

1
2
3
4
5
6
7
8
if (exists $books{"dino"}) { # exists 函数可以用于检查哈希中是否存在某个键
    print "Hey, there's a library card for dino!\n";
}
my $person = "betty";
delete $books{$person}; #删除指定键值,如果没有这样的键值,则直接返回,不会出现任何警告和错误信息。
                                              
print "PATH is $ENV{PATH}\n"; #检查运行环境的PATH变量

 

 

哈希赋值

根据现有的哈希建立一个反序的哈希:

1
my %inverse_hash = reverse %any_hash;

需要注意的是,这个技巧最好是在确定原始哈希的值是唯一的情况下使用。

为了区分哈希表中的键-值对,Perl语言可以使用胖箭头("=>")代替逗号,例如

1
2
3
4
5
6
my %last_name = (
    'fred' => 'flintstone',
    'dino' => undef,
    barney => 'rubble', #使用胖箭头的时候,键的引号可以省略
    betty => 'rubble', #列表结尾有一个额外的逗号,便于维护列表
);

 

哈希函数

Keys和values函数——keys函数能返回哈希的键列表,而values函数能返回对应的值列表

1
2
3
4
5
my %hash = ('a' => 1, 'b' => 2, 'c'=>3);
my @k = key %hash; #返回 'a', 'b', 'c'
my @v = values %hash; #返回 1, 2, 3
                               
my $count = keys %hash; # 标量上下文中返回3,也就是键值的个数

 

each函数可以用于需要迭代处理整个哈希列表的情形

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

 

六、 正则表达式

正则表达式中Unicode属性

Unicode字符不只是简单的字节序列,除了字节组合之外,它还附带着属性信息。所以除了匹配字符本身以外,我们还能根据字符的属性来匹配:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
if (/\p{Space}/) { # 总共有26个不同字符带此属性
    print "The string has some whitespace.\n";
}
if (/\p{Digit}/) { # 总共有411个不同的数字字符
    print "The string has some whitespace.\n";
}
if (/\p{Hex}/) { # 匹配十六进制字符集[0-9A-Fa-f]
    print "The string has some whitespace.\n";
}
if (/\P{Space}/) { # 大写的'P'表示反向选择
    print "The string has some whitespace.\n";
}

 

元字符

点号(.)可以匹配任意一个字符,除了换行符'\n'。

反斜线(\)会使接下来的下一个字符失去特殊作用,仅仅代表下一个字符本身。

 

量词

星号(*)用来匹配前面的条目零次或多次。例如,'.*'可以匹配换行符以外的任意字符串。

加号(+)用来匹配前面的条目一次或多次。

问号(?)用来匹配前面的条目零次或一次。

 

模式分组和反向引用

在正则表达式中,圆括号‘()’的作用是对字符串分组。例如,模式/fred+/会匹配像fredddddddd这样的字符串,而/(fred)+/可以匹配fredfredfred这样的字符串。

通过反向引用,我们可以构成一些有趣的模式

1
2
3
4
$_ = "abba";
if (/(.)\1/) { # 匹配'bb'
    print "It matched same character next to it self!\n";
}

一个正则表达式中可以有多个模式分组,甚至是嵌套,那么该如何区分哪个括号是第几组呢?其实只要一次点算左括号的序号就行了——

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

如果反向引用后跟着的是数字怎么办呢?(比如\111引用的是\1、\11还是\111呢)其实Perl会尽可能地创建最多数量的反向引用。不过从Perl 5.10开始支持一种新的反向引用写法:\g{N}的形式。

1
2
3
4
5
6
7
8
9
10
11
use 5.010
$_ = "aa11bb"
if (/(.)\g{1}11/) { # N代表想要反向引用的组号。
    print "It matched\n";
}
$_ = "xaa11bb"
if (/(.)(.)\g{-11}11/) { # 反向引用的组号可以是相对位置
    print "It matched\n";
}

 

择一匹配

竖线(|)通常可以读成“或”,意思是要么匹配左边的内容,要么匹配右边的内容。

例如可以使用/fred(|\t)+barney/这样的模式来匹配fred和barney之间出现一次或以上空格、制表符或两者混合的字符串。

 

字符集

字符集指的是一组可能出现的字符,通过写在方括号([ ])内表示。它只匹配单个字符,但可以是字符集中列出的任何一个。比如[abcwxyz]可以匹配这7个字符中的任意一个。为方便起见,你也可以用连字符“-”表示始末范围,如[a-cw-z]。

有时候,指定字符集以外的字符会比指定字符集内的字符更容易。可以在字符集开头的地方加上脱字符(caret,^)来表示这些字符除外。

有些字符集出现的频率非常高,所以我们给它们设定了简写形式。例如:

\d 表示任意一个数字 (注意,Perl5.6之前它严格等同于字符集[0-9],但它现在还能匹配比较少见的其他Unicode字符集中的数字)

\s 能匹配任意空白符,效果上等同于Unicode属性\p{Space} (Perl5.6之前仅能匹配换页符、水平制表符tab、换行符、回车符和空格符本身)

\w 一直被称为“word”字符,尽管它匹配的并不是严格意义上的单词字符。ASCII语义下它匹配的是[a-zA-Z0-9_]

在从ASCII到Unicode转变的过程中,怎么区分前后不同意义的字符集成了一个需要解决的问题。因此Perl 5.14引入了一种新的修饰符\a,写在正则表达式末尾,表示按照ASCII语义展开,例如 /HAL-[\d]+/a 可以匹配“HAL-9000”这样的字符串

 

反义简写

有时候你也许需要指定以上几种简写以外的字符,例如[^\d]、[^\w]或是[^\s]这样的模式。为此我们引入了它们的大写版本表示否定意义,如\D、\W、\S。

有种比较特别的复合字符集[\d\D],表示任意数字或非数字字符。也就是说,它能匹配任意字符,甚至包括换行符!(“.”只能匹配换行符以外的所有字符)

 

用m/ /进行匹配

当使用“/”作为正则表达式的定界符时,可以省略前面的模式匹配操作符“m”。就像“qw//”操作符一样,我们可以选择任何成对的定界符。当然我们应该明智地选择成对的或者模式中不会出现的字符作为定界符。

 

模式匹配修饰符

/i表示进行大小写无关的匹配

/s 可以使“.”匹配任意字符,包括换行符

/x 允许模式中加入空白符以便于阅读,不过原来表示空白的字符就必须加上转义字符“/”

1
2
3
4
5
6
7
8
9
10
11
12
13
print "Would you like to play a game?";
chomp($_ = <STDIN>);
if (/yes/i) { # 大小写无关的匹配
    print "In that case, I recommended taht you go bowling.\n";
}
$= "I saw Barney\ndown at the bowling alley\nwith Fed\nlast night.\n";
if (/Barney.*Fred/s) { # 匹配成功
    print "That string mentions Fred after Barney!\n";
}
/-?[0-9]+\.?[0-9]*/ # 挤在一起很难看清什么意思
/ -? [0-9]+ \.? [0-9]* /x #加入空白后好多了

当然如果我们同时需要多个修饰符,只要把它们接在一起放在模式末尾就可以了。(不用在意先后次序)

 

模式匹配中的字符解释方式

/a 表示ASCII方式

/u 表示采取Unicode方式

/l 表示遵从本地化语言的设定

 

锚位

Perl 5中\A锚位匹配字符串的绝对开头,\z匹配字符串的绝对末尾,\Z是行末锚位,它允许后面出现换行符。

1
2
3
m{\Ahttps?://}i # 匹配字符串是否以https开头
m{\.png\z}i #匹配以.png结束的字符串
m{\.png\Z}i #匹配以.png结束的行

在Perl 4 中,所用的表示字符串开头锚位的是脱字符(^),用于表示字符串结尾锚位的是$。这种写法到了Perl 5依然可用,不过已经演化成了行首和行末锚位。

如果需要对多行内容进行匹配怎么办呢?你可以用$锚位和/m修饰符表示对多行内容进行匹配。

除此之外,还有“\b”单词边界锚位,它能匹配任何单词的首尾。“\B”锚位用于匹配其他所有\b不能匹配的位置。

 

绑定操作符=~

默认情况下模式匹配的操作对象是$_,绑定操作符拿右边的模式来匹配左边的字符串。

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

 

捕获变量

圆括号“()”出现的地方一般都会触发正则表达式引擎捕获匹配到的字符串。捕获组会把匹配圆括号中模式的字符串保存到相应的地方。与反向引用类似,正则表达式根据“(”出现的次序一次对捕获组编号为1、2、3……

1
2
3
4
$_ = "Hello there, neighbor";
if (/\s([a-zA-Z]+),/) { # 捕获空白符和逗号之间的单词
    print "the word was $1\n"; # 打印the word was there
}

这些捕获变量通常在下次成功匹配前会一直有效,所以$1之类的捕获变量只应该在模式匹配后的数行内使用。

有时候圆括号仅仅使用来分组,我们需要关掉这个功能

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

为了彻底解决维护诸如$1、$2这样的变量带来的麻烦,不用每次修改模式都从新把圆括号数一遍,Perl 5.10增加了对捕获内容直接命名的写法。最终捕获到的内容会保存在特殊哈希%+里面,其中的键就是在捕获时用的特殊标签,对应的值则是捕获的字符串。

1
2
3
4
5
6
use 5.010;
my $names = 'Ferd or Barney';
if ($name =~ m/(?<name1>\w+) (?:and|or) (?<name2>\w+)/) {
    say "I saw $+{name1} and $+{name2}"; #结果输出 I saw Fred and Barney
}

有三个免费的捕获变量,就算不加圆括号也能使用。$&里保存的是匹配成功的整个区段;$`里面保存匹配区段之前的内容哦;$'保存匹配区段之后的内容

1
2
3
if ("Hello there, neighbor" =~ /\s(\w+),/) {
    print "That was ($`)($&)($').\n"; # 程序运行时会把字符串显示为(Hello)(there,)(neighbor)
}

如果你使用的是Perl 5.10或以上的版本,那么就更加方便了。修饰符/p灰针对特定的正则表达式开启类似的自动捕获变量,但它们的名字不再是$`、$&、$',而是用${^PREMATCH}、${^MATCH}、${^POSTMATCH}来表示。

 

通用量词

到目前为止,我们已经见过三个量词*、+、?如果这三个量词都不符合需要,还可以使用花括号的形式制定具体的重复次数。

因此,模式/a{5,15}/可匹配重复出现5到15次的字母a。

所以*相当于{0,};+相当于{1,};?相当于{0,1}。

 

优先级

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

 

一个模式测试程序

1
2
3
4
5
6
7
8
9
#! /usr/bin/perl
while (<>) { # 每次读入一行输入
    chomp;
    if (/YOUR_PATTERN_GOES_HERE/) {
        print "Matched: |$`<$&>$'|\n"; #特殊捕获变量
    } else {
        print "No match: |$_|\n";
    }
}

 

------分隔线----------------------------