在介绍元字符之前,先了解一下正则表达式在Perl中的简单运用方式:
#格式转换:utf-8 转换成gbk格式
use encoding 'utf8', STDOUT=>'gbk'; #说明:采用的Perl版本为5.012, 支持encoding模式,如果是其它版本可能会出错
$_ = "yabba dabba doo"; #被查找的特殊变量一定放在特殊变量里$_
if(/abba/){
#界定符可以不使用双斜杠//
print "配上了\n";
}
else{
print "没配上\n";
}
当模式匹配的对象是$_的内容,只需要把模式写在一对斜线(/)中就可以了,模式本身就是一串简单的字符序列。由于模式匹配通常用来返回真或者假,所以往往会用在if或者while的条件表达式里。
模式匹配除了匹配简单的直接量字符串,还可以匹配一些特殊字符,称之为元字符,它们在正则表达式中代表着特殊含义。
普通字符 | 含义 |
---|---|
点号(.) | 匹配任意一个字符的通配符(换行符“\n”除外) |
反斜线(\) | 表示转义,任意元字符前加反斜线,都会失去作用成为普通字符;要匹配真正的反斜线,需用两个反斜线表示 |
圆括号() | 模式分组——对字符串进行分组,以便反向引用 |
或(|) | 择一匹配——要么匹配左边内容,要么匹配右边内容 |
字符集 | 字符集——通常写在方括号[ ]内表示,只匹配单个字符,可以是字符集中列出的任意一个 |
脱字符(^) | 一般在字符集开头的地方加上,表示这些字符除外 |
连字符(-) | 在字符集中表示始末范围;在字符集括号外无特殊意义 |
[^n\-z] | 表示匹配n,连字符与z以外的任意字符 |
(贪婪)数量元字符 | 含义 | 贪婪量词 | 含义 |
---|---|---|---|
星号(*) | 匹配符号前面的条目0~任意次 | *? | 匹配的字符串越短越好 |
.* | 匹配任意字符0~任意次(换行符除外) | .*? | 匹配的字符串越短越好 |
加号(+) | 匹配符号前面的条目1~任意次 | +? | 匹配的字符串越短越好 |
问号(?) | 匹配符号前面的条目0~1次 | ?? | 优先考虑0次的情况 |
花括号({ }) | 指定具体的重复次数范围 | { }? | – |
{n,m} | 匹配n ~ m个 | {n,m}? | 优先匹配n次的情况 |
{n} | 匹配 n 个 | – | – |
{n,} | 匹配 n ~ 任意个 | {n,}? | 优先匹配n次的情况 |
{,m} | 匹配 0 ~ m个 | {,m}? | 优先匹配0次的情况 |
$_ = "abcdddddeee";
if(/a?b*c+d{
2,5}e{
2,}/){
#大括号——指定通用量词
print "配上了\n";
}
字符集简写(大写反义简写) | 含义 |
---|---|
\d | 匹配任意一个数字,等价于字符集[0-9] |
\s | 匹配任意空白符,等价于字符集[\t\n\r\f ] |
\w | 匹配任意数字、字母和下划线,等价于[A-Za-z0-9_] |
\D | 匹配非数字字符 |
\S | 匹配非空白字符 |
\W | 匹配非单词字符 |
正则表达式特性 | 示例 |
---|---|
圆括号(分组或捕获) | (…),(?: …),(? |
量词 | a*,a+,a?,a{n,m} |
锚位和序列 | abc,^,$,\A,\b,\z,\Z |
择一竖线 | a|b|c |
原子 | a,[abc] ,\d,\1,\g{2} |
#优先级
$_ = "barney";
if(/^(fred|barney)$/){
print "配上了\n";
}
在正则表达式中,可以使用圆括号()对字符串进行分组,我们可以用反向引用来引用圆括号中的模式所匹配的文字,这个行为称之为捕获组。反向引用不必紧接在对应的捕获组括号后边,其写法是在反斜线后边接上数字编号,比如\1、\2这样,相应的数字表示对应顺序的捕获组,只要依次点算左括号(包括嵌套括号)的序号即可。
use 5.010; #引入新版本写法
#模式分组(反向引用——相对、绝对)
if(/(fred)+/){
print "找到了\n";}else{
print "没找到\n";} #匹配fredfredfred这样的字符串
if(/(.)(..)\1\2/){
print "找到了\n";}else{
print "没找到\n";}
#模式分组——新写法
if(/(.)(..)\g{
1}\g{
2}/){
print "找到了\n";}else{
print "没找到\n";} #花括号——绝对引用,新写法
if(/(.)(..)\g{
-1}/){
print "找到了\n";}else{
print "没找到\n";} #负号——相对引用,代表前一个括号
从Perl 5.010版本开始,支持一种新的反向引用写法。不在只是用简单的反斜线和组号,而是用 \g{N} 这种形式,N是方向引用的组号这种写法的好处是,组号可以使用负数。
在TextPad上编码采用的是UTF-8模式,而控制端口采用的是GBK模式,为了统一编码格式不至于出现打印乱码,需在文件开头添加代码:
前述的模式写法都是采用双斜线的写法表示,事实上,这是m//(模式匹配操作符)的简写。类似qw//简写提到的,可以选择任何成对的定界符。所以,我们可以写成m(fred),m{fred},m
说明:简写省略m,仅限于使用双斜线定界符;如果使用其它定界符,m不可省略。
Perl中有多种模式匹配修饰符,也称标志(flag)。它们是一些追加在模式表达式末尾定界符后面的字母,用来改变默认的匹配行为。具体见下表:其中字符解释意义的修饰符是由Perl 5.014开始加入。
字符集简写(大写反义简写) | 含义 |
---|---|
\i | 进行大小写无关的匹配 |
\s | 匹配任意字符,它会使点号(.)匹配任意字符,包括换行符 |
\x | 加入空白符,允许在模式中任意添加空白符,增加易读性 |
\g | 可以执行全局匹配 |
\m | 可以执行多行匹配 |
\a | 在ASCII范围内进行字符解释 |
\u | 在Unicode范围内进行字符解释 |
\l | 在ocale范围内进行字符解释 |
说明:正则表达式/m修饰符:
/m修饰符的作用是修改 ^ 和 $ 在正则表达式中的作用,让它们分别表示行首和行尾
。#单一选项修饰符——/x
print "请输出一个数:";
chomp($_ = <STDIN>);
if(/-? #?_0个或1个负号
\d+ #\d+_1个或多个数字,等价于[0-9]+
\.? #0个或1个小数点
\d+ #1个或多个数字
/x){
#\x_加入空白符,易读
print "你输入的数是:$_\n";
}
else{
print "你输入的不是数:$_\n";
}
#组合选项修饰符——/x
$_ = "I saw Barney\ndown at the bowling alley\nwith fred\nlast night.\n";
print;
if(/barney #找barney
.* #任意字符,包括换行符\n
fred #找fred
/isx){
#/s_匹配任意字符,即点号也会匹配换行符\n;/i_大小写无关匹配;/x_加入空白符
print "找到了!\n";
}
else{
print "没找到!\n";
}
默认情况下,给定的模式如果不匹配字符串的开头,就会顺移到下一个字符继续尝试。而通过给定锚位,可以让模式仅在字符串的指定位置匹配。
锚位并不局限于字符串首尾,还可应用于单词的首位,又称“整词匹配”。
锚位字符 | 含义 |
---|---|
脱字符(^),锚位\A | 表示字符串开头锚位(在字符集方括号中做取反用) |
美元符($),锚位\z | 表示字符串结尾锚位 |
\b | 单词边界锚位,只匹配每组字符的开头或结尾 |
\B | 非单词边界锚位 |
$_ = "fred banny and fred";
if(/^fred/){
print "配上了\n";}else{
print "没配上\n";} #^_在字符集外,该符号表示字符串的开头锚位
$_ = "fred";
if(/^fred$/){
print "配上了\n";}else{
print "没配上\n";} #$符号表示字符串的结尾锚位
$_ = "man fred man";
if(/\bfred\b/){
print "配上了\n";}else{
print "没配上\n";} #\b表示单词的边界锚位,该例单词开头为f,结尾为d
$_ = "search";
if(/$_ =~ \bsearch\B/){
print "配上了\n";}else{
print "没配上\n";} #\B表示非单词边界锚位
#绑定操作符(=~)——运用
$some_other = "I dream of betty rubble.";
if($some_other =~ /\brub/){
print "配上了\n";}else{
print "没配上\n";}
编译运行:
配上了
配上了
配上了
没配上
配上了
默认情况下,模式匹配的操作对象是$_,绑定操作符(=~)告诉Perl,右边的模式是用来匹配左边的字符串,而不是匹配 $ _。当使用绑定操作符时,可以不使用默认变量 $ _,自由进行定义。如上例
正则表达式内部同样可以进行双引号形式的内插:下述代码中$what就等效于/^(Larry)/,也就是寻找Larry开头的行。
my $what = "Larry";
while(<>){
if(/^($what)/){
#模式串中的内插
print "We saw $what in beginning of $_";
}
}
编译运行:
Larry and me #Larry开头
We saw Larry in beginning of Larry and me
Laty is good
larry
Larry is a good man #Larry开头
We saw Larry in beginning of Larry is a good man
^Z
首行添加如下代码,将UTF-8格式的代码文件转换成GBK格式,在终端(GBK格式)导出,避免编译时乱码。
use encoding 'utf8', STDOUT=>'gbk'; #说明:采用的Perl版本为5.012, 支持encoding模式,如果是其它版本可能会出错
圆括号可以用来进行分组,圆括号同样也可以用来进行捕获变量,这捕获变量保存的是字符串,在Perl里,它们的名称就是$1和$2这样的的形式,模式中有多少个捕获括号,就有多少个对应的捕获变量可用。
反向引用\2与捕获变量$2的区别:
捕获变量的存续期:可存活到下次匹配成功为止。失败的匹配并不会改变上次成功匹配时得到的内容。
print "Hello\n";
$_ = "Hello there, neigbor";
if(/\s(\w+),/){
print "$1\n"; #打印:there
}
if(/(\S+) (\S+), (\S+)/){
print "$1 $2 $3\n"; #打印:Hello there neigbor
}
my $dino = "I fear that I'll be extinct after 1000 years.";
if($dino =~ /(\d*) years/){
print "$1\n"; #打印:1000
}
正则表达式中的圆括号通常都会触发变量捕获,但是有时候却需要关闭这个功能,而仅仅是用它来进行分组。这就需要用到不捕获圆括号(?:)。
$_ = "one two three four five";
if(/(\S+) (\S+) (\S+)/){
#?:_只用来分组,不用来捕获
print "$2 $3\n"; #打印:two three
}
**说明**:尽管上述打印结果未列出$1,但是第一个捕获变量还是要留给他
$_ = "one two three four five";
if(/(?:\S+) (\S+) (\S+)/){
#(?:)——只用来分组,不用来捕获
print "$1 $2 $3\n"; #打印:two three
}
为了避免记忆$1之类的数字变量,Perl5.010增加了对捕获内容直接命名的写法。最终捕获到的内容会保存在特殊哈希%+里:其中的键就是在捕获时用的特殊标签,对应的值为被捕获到的字符串。具体的写法是:(?。
在使用捕获标签后,反向引用的写法也会随之发生变化。之前所用的\1或者g{1}这样的写法,现在可以改为 \g{Label} 这样的写法,当然:k。
my $names = "Fred or Barney";
if($names =~ /(?<name1>\w+) (?:and|or) (?<name2>\w+)/){
#?<>_命名的捕获
#print "$1, $2\n";
print "$+{name1}, $+{name2}\n"; #打印: Fred, Barney
}
my $names = "Fred Flinstones and Wilma Flinstones";
if($names =~ /(?<last_name>\w+) and \w+ \g{
last_name}/){
#在反向引用中使用命名变量,也可改g{
}为k<>
print "$+{last_name}\n"; #打印:Flinstones
}
Perl中三大免费的自动捕获变量:
$_ = "one two three, four five six";
if(/\s(\w+),/){
print "$1\n";
print "$&\n"; #自动匹配字符变量(捕获全部匹配到的)
print "$'\n"; #单引号(捕获匹配节点后面的其余字符串)
print "$`\n"; #反引号(捕获匹配节点前面的其余字符串)
print "$`$&$'\n"; #组合(完整子符串)
print "$`<$&>$'\n"; #尖括号(标识出捕获到的字符串部分)
}
编译运行:
three
three,
four five six
one two
one two three, four five six
one two< three,> four five six
正则表达式测试程序:
#这是一个模式测试程序
while(<>){
chomp;
if(/我的正则表达式/){
# \s\d+
print "配上了:|$`<$&>$'|\n";
}
else{
print "没配上\n";
}
}
编译运行:(正则表达式:/\s\d+/)
i have 100 dollars #键入
配上了:|i have< 100> dollars| #打印结果
the number is 1245
配上了:|the number is< 1245>|