想必学习perl的人,对基础正则表达式都已经熟悉,因此学习perl正则会很轻松。这里我不打算解释基础正则的内容,而是直接介绍基础正则中不具有的但perl支持的功能。关于基础正则表达式的内容,可参阅基础正则表达式。html
另外,本系列只介绍匹配操做,关于内容替换,由于和学习使用perl正则并没有多大关系,因此替换相关的将在下一篇文章单独解释。java
这里推荐一个学正则很是好的资料:stackflow上关于各类语言(perl/python/.net/java/ruby等等)的正则的解释、示例,这里收集的都是对问题解释的很是清晰且很是经典的回答。在我学习perl正则的时候,对有些功能实在理解不了(想必你也必定会),就会从这里找答案,而它,也历来没让我失望:https://stackoverflow.com/questions/22937618/reference-what-does-this-regex-mean/22944075#22944075python
如下是perl正则的man文档:正则表达式
新建一个文件做为perl脚本文件,在其首行写上#!/usr/bin/perl
,它表示用perl做为本文件的解释器。写入一些perl程序后,再赋予执行权限就能够执行了,或者直接使用perl命令去调用这个脚本文件,前面的两个过程均可以省略,这和shell脚本的方式是彻底同样的,无非是将bash替换为了perl,想必各位都理解。shell
1.print用来输出信息,至关于shell中的echo命令,但须要手动输入换行符"\n"进行换行。安全
例如:ruby
#!/usr/bin/perl print "hello world\n"; # 注意每一句后面都使用分号结尾
保存后,执行它(假设脚本文件名为test.pl):bash
$ chmod +x test.pl $ perl test.pl
2.变量赋值app
perl中的变量能够不用事先声明,能够直接赋值甚至直接引用。注意变量名前面老是须要加上$符号,不管是赋值的时候仍是引用的时候,这和其它语言不太同样。less
#!/usr/bin/perl $name="longshuai"; $age=18; print "$name $age \n";
3.if语句用来判断,语法格式为:
if(condition){ body }else{ body }
例如:
$age = 18; if($age <= 20){ print "age less than 20\n"; } else { print "age greate than 20\n"; }
4.默认参数变量
在perl中,对于须要参数的函数或表达式,但却没有给参数,这是将会使用perl的默认参数变量$_
。
例如,下面的print原本是须要参数的,可是由于没有给参数,print将输出默认的参数变量$_
,也就是输出"abcde"。
$_="abcde"; print ;
perl中使用$_
的地方很是多,后文还会出现,不过用到的时候我会解释。
5.读取标准输入
perl中使用一对尖括号格式的<STDIN>
来读取来自非文件的标准输入,例如来自管道的数据,来自输入重定向的数据或者来自键盘的输入。须要注意的是,<STDIN>
读取的输入会自带换行符,因此print输出的时候不要加上额外的换行符。
例如,在test.pl文件中写入以下内容:
#!/usr/bin/perl $data=<STDIN>; print "$data";
而后用管道传递一行数据给perl程序:
echo "abcdefg" | perl test.pl
只是须要注意,将<STDIN>
赋值给变量时,将只能读取一行(遇到换行符就结束读取)。例以下面的perl将读取不了"hijklmn"。
echo -e "abcdefg\nhijklmn" | perl test.pl
若是想要读取多行标准输入,就不能将其赋值给变量,而是使用foreach来遍历各行(此处不介绍其它方式):
foreach $line (<STDIN>){ print "$line"; }
以上就是foreach的语法:
<STDIN>
读取的多行数据就是一个列表,每一行都是列表中的一个元素;能够省略$line,这时就采用默认的参数变量$_
,因此如下两个表达式是等价的:
foreach (<STDIN>){ print "$_"; } foreach $_ (<STDIN>){ print "$_"; }
6.读取文件中的数据
正则强大的用处就是处理文本数据,因此必需要说明perl如何读取文件数据来作正则匹配。
咱们能够将文件做为perl命令行的参数,perl会使用<>
去读取这些文件中的内容。
foreach (<>){ print "$_"; }
执行的时候,只要把文件做为perl命令或脚本文件的参数便可:
perl test.pl /etc/passwd
7.去掉行尾分隔符
因为<>
和<STDIN>
读取文件、读取标准输入的时候老是自带换行符,不少时候这个自带的换行符都会带来格式问题。因此,有必要在每次读取数据时将行尾的换行符去掉,使用chomp便可。
例如:
foreach $line (<STDIN>) { chomp $line; print "$line read\n"; }
如下是执行结果:
[root@xuexi ~]# echo -e "malongshuai gaoxiaofang" | perl 26.plx malongshuai gaoxiaofang read
若是上面的例子中不加上chomp,那么执行结果将像下面同样:
[root@xuexi perlapp]# echo -e "malongshuai gaoxiaofang" | perl 26.plx malongshuai gaoxiaofang read
显然,输出格式和print语句中期待的输出格式不同。
前面说过,能够省略$line
,让其使用默认的参数变量$_
,因此能够这样读取来自perl命令行参数文件的数据:
foreach (<>) { chomp; print "$_ read\n"; }
8.命令行的操做模式
其实就是一行式。perl命令行加上"-e"选项,就能在perl命令行中直接写perl表达式,例如:
echo "malongshuai" | perl -e '$name=<STDIN>;print $name;'
由于perl最为人所知的就是它应用了各类符号的组合,让人看着怪异无比,而这些符号放在命令行中极可能会被shell先解析,因此强烈建议"-e"后表达式使用单引号包围,而不是双引号。
更建议,若是能够,不要使用perl命令行的方式,调试起来容易混乱。
使用=~
符号表示要用右边的正则表达式对左边的数据进行匹配。正则表达式的书写方式为m//
。关于m//
,其中斜线能够替换为其它符号,规则以下:
m()
,m{}
,相同的标点类,m!!
,m%%
等等//
等价于m//
/http:\/\//
转换成m%http://%
因此要匹配内容,有如下两种方式:
data =~ m/reg/
,能够明确指定要对data对应的内容进行正则匹配/reg/
,由于省略了参数,因此使用默认参数变量,它等价于$_ =~ m/reg/
,也就是对$_
保存的内容进行正则匹配perl中匹配操做返回的是匹配成功与否,成功则返回真,匹配不成功则返回假。固然,perl提供了特殊变量容许访问匹配到的内容,甚至匹配内容以前的数据、匹配内容以后的数据都提供了相关变量以便访问。见下面的示例。
例如:
1.匹配给定字符串内容
$name = "hello gaoxiaofang"; if ($name =~ m/gao/){ print "matched\n"; }
或者,直接将字符串拿来匹配:
"hello gaoxiaofang" =~ m/gao/;
2.匹配来自管道的每一行内容,匹配成功的行则输出
foreach (<STDIN>){ chomp; if (/gao/){ print "$_ was matched 'gao'\n"; } }
上面使用了默认的参数变量$_
,它表示foreach迭代的每一行数据;上面还简写的正则匹配方式/gao/
,它等价于$_ =~ m/gao/
。
如下是执行结果:
[root@xuexi perlapp]# echo -e "malongshuai gaoxiaofang" | perl 26.plx malongshuai gaoxiaofang was matched 'gao'
3.匹配文件中每行数据
foreach (<>){ chomp; if(/gao/){ print "$_ was matched 'gao'\n"; } }
4.若是想要输出匹配到的内容,可使用特殊变量$&
来引用匹配到的内容,还可使用$`
引用匹配前面部分的内容,$'
引用匹配后面部分的内容
例如:
aAbBcC =~ /bB/
因为匹配的内容是bB,匹配内容以前的部分是aA,匹配以后的部分是cC,因而能够看做下面对应关系:
(aA)(bB)(cC) | | | $` $& $'
如下是使用这三个特殊变量的示例:
$name="aAbBcC"; if($name =~ /bB/){ print "pre match: $` \n"; print "match: $& \n"; print "post match: $' \n"; }
须要注意的是,正则中通常都提供全局匹配的功能,perl中使用修饰符/g
开启。当开启了全局匹配功能,这3个变量保存的值须要使用循环语句去遍历,不然将只保存第一次匹配的内容。例如:
$name="aAbBcCbB"; if($name =~ /bB/g){ # 匹配完第一个bB就结束 print "pre match: $` \n"; print "match: $& \n"; print "post match: $' \n"; } while($name =~ /bB/g){ # 将迭代两次 print "pre match: $` \n"; print "match: $& \n"; print "post match: $' \n"; }
从这里开始,正式介绍perl支持的正则。
出于方便,我所有都直接在perl程序内部定义待匹配的内容,若是想要匹配管道传递的输入,或者匹配文件数据,请看上文获取操做方法。
为了完整性,每一节中我都是先把一大堆的内容列出来作个简单介绍,而后再用示例解释每一个(或某几个)。但perl正则的内容太多,并且不少功能先后关联,因此若是列出来的内容没有在同一小节内介绍,那么就是在后面须要的地方介绍。固然,也有些没什么用或者用的不多的功能(好比unicode相关的),通篇都不会介绍。
指定模式匹配的修饰符,能够改变正则表达式的匹配行为。例如,下面的i就是一种修饰符,它让前面的正则REG匹配时忽略大小写。
m/REG/i
perl总共支持如下几种修饰符:msixpodualngc
i
:匹配时忽略大小写g
:全局匹配,默认状况下,正则表达式"abc"匹配"abcdabc"字符串的时候,将之匹配左边的abc,使用g将匹配两个"abc"c
:在开启g的状况下,若是匹配失败,将不重置搜索位置m
:多行匹配模式s
:让.
能够匹配换行符"\n",也就是说该修饰符让.
真的能够匹配任意字符x
:容许正则表达式使用空白符号,省得让整个表达式难读难懂,但这样会让本来的空白符号失去意义,这是可使用\s
来表示空白o
:只编译一次正则表达式n
:非捕获模式p
:保存匹配的字符串到${^PREMATCH}
、${^MATCH}
、${^POSTMATCH}
中,它们在结果上对应$`
、$&
和$'
,但性能上要更好a
和u
和l
:分别表示用ASCII、Unicode和Locale的方式来解释正则表达式,通常不用考虑这几个修饰符d
:使用unicode或原生字符集,就像5.12和以前那样,也不用考虑这个修饰符这些修饰符能够连用,连用时顺序可随意。例以下面两行是等价的行为:全局忽略大小写的匹配行为。
m/REG/ig m/REG/gi
上面的修饰符,本节介绍igcmsxpo这几个修饰符,n修饰符在后面分组捕获的地方解释,auld修饰符和字符集相关,不打算解释。
该修饰符使得正则匹配的时候,忽略大小写。
$name="aAbBcC"; if($name =~ m/ab/i){ print "pre match: $` \n"; # 输出a print "match: $& \n"; # 输出Ab print "post match: $' \n"; # 输出BcC }
g修饰符(global)使得正则匹配的时候,对字符串作全局匹配,也就是说,即便前面匹配成功了,还会继续向后匹配,看是否还能匹配成功。
例如,字符串"abcabc",正则表达式"ab",在默认状况下(不是全局匹配)该正则在匹配到第一个ab后就结束了,若是使用了g修饰符,匹配完第一个ab,还会继续向后匹配,并且正好还能匹配到第二个ab,因此最终有两个ab被匹配成功。
要验证屡次匹配,须要使用循环遍历的方式,而不能用if语句:
$name="aAbBcCaBc"; while($name =~ m/ab/gi){ print "pre match: $` \n"; print "match: $& \n"; print "post match: $' \n"; }
执行它,将输出以下内容:
pre match: a match: Ab post match: BcCabd pre match: aAbBcC match: ab post match: d
如下内容,若是仅仅只是为了学perl正则,那么能够跳过,由于很难,若是是学perl语言,那么能够继续看下去。
实际上,在开启了g全局匹配后,perl每次在成功匹配的时候都会记下匹配的字符位移,以便在下次匹配该内容时候,能够从指定位移处继续向后匹配。每次匹配成功后的位移值(pos的位移从0开始算,0位移表明的是第一个字符左边的位置),均可以经过pos()函数获取。若是本次匹配致使位移指针重置,pos将返回undef。
$name="123ab456"; $name =~ m/\d\d/g; # 第一次匹配,匹配成功后记下位移 print "matched string: $&, position: ",pos $name,"\n"; $name =~ m/\d\d/g; # 第二次匹配,匹配成功后记下位移 print "matched string: $&, position: ",pos $name,"\n";
执行它,将输出以下内容:
matched string: 12, position: 2 matched string: 45, position: 7
因为匹配失败的时候,正则匹配操做会返回假,因此能够做为if或while等的条件语句。例如,改成while循环屡次匹配:
$name="123ab456"; while($name =~ m/\d\d/g){ print "matched string: $&, position: ",pos $name,"\n"; }
默认全局匹配状况下,当本次匹配失败,位移指针将重置到起始位置0处,也就是说,下次匹配将从头开始匹配。例如:
$txt="1234a56"; $txt =~ /\d\d/g; # 匹配成功:12,位移向后移两位 print "matched $&: ",pos $txt,"\n"; $txt =~ /\d\d/g; # 匹配成功:34,位移向后移两位 print "matched $&: ",pos $txt,"\n"; $txt =~ /\d\d\d/g; # 匹配失败,位移指针回到0处,pos()返回undef print "matched $&: ",pos $txt,"\n"; $txt =~ /\d/g; # 匹配成功:1,位移向后移1位 print "matched $&: ",pos $txt,"\n";
执行上述程序,将输出:
matched 12: 2 matched 34: 4 matched 34: matched 1: 1
若是"g"修饰符下同时使用"c"修饰符,也就是"gc",它表示全局匹配失败的时候不重置位移指针。也就是说,本次匹配失败后,位移指针会向后移一位,下次匹配将从后移的这个位置处开始匹配。当位移移到告终尾,将没法再移动,此时位移指针将一直指向最后一个位置。
$txt="1234a56"; $txt =~ /\d\d/g; print "matched $&: ",pos $txt,"\n"; $txt =~ /\d\d/g; print "matched $&: ",pos $txt,"\n"; $txt =~ /\d\d\d/gc; # 匹配失败,位移向后移1位,$&和pos()保留上一次匹配成功的内容 print "matched $&: ",pos $txt,"\n"; $txt =~ /\d/g; # 匹配成功:5,位移向后移1位 print "matched $&: ",pos $txt,"\n"; $txt =~ /\d/g; # 匹配成功:6,位移向后移1位 print "matched $&: ",pos $txt,"\n"; $txt =~ /\d/gc; # 匹配失败:位移没法再后移,将一直指向最后一个位置 print "matched $&: ",pos $txt,"\n";
执行上述程序,将输出:
matched 12: 2 matched 34: 4 matched 34: 4 matched 5: 6 matched 6: 7 matched 6: 7
继续上面的问题,若是第三个匹配语句不是\d\d\d
,而是"\d",它匹配字母a的时候也失败,不用c修饰符的时候会重置位移吗?显然是不会。由于它会继续向后匹配。因此该\G
登场了。
默认全局匹配状况下,匹配时是能够跳过匹配失败的字符继续匹配的:当某个字符匹配失败,它会后移一位继续去匹配,直到匹配成功或匹配结束。
$txt="1234ab56"; $txt =~ /\d\d/g; print "matched $&: ",pos $txt,"\n"; $txt =~ /\d\d/g; print "matched $&: ",pos $txt,"\n"; $txt =~ /\d/g; # 字母a匹配失败,后移一位,字母b匹配失败,后移一位,数值5匹配成功 print "matched $&: ",pos $txt,"\n"; $txt =~ /\d/g; # 数值6匹配成功 print "matched $&: ",pos $txt,"\n";
执行上述程序,将输出:
matched 12: 2 matched 34: 4 matched 5: 7 matched 6: 8
能够指定\G
,使得本次匹配强制从位移处进行匹配,不容许跳过任何匹配失败的字符。
\G
全局匹配成功,位移指针天然会后移\G
全局匹配失败,且没有加上c修饰符,那么位移指针将重置\G
全局匹配失败,且加上了c修饰符,那么位移指针将卡在那不动例如:
$txt="1234ab56"; $txt =~ /\d\d/g; print "matched $&: ",pos $txt,"\n"; $txt =~ /\d\d/g; print "matched $&: ",pos $txt,"\n"; $txt =~ /\G\d/g; # 强制从位移4开始匹配,没法匹配字母a,但又不容许跳过 # 因此本次\G全局匹配失败,因为没有修饰符c,指针重置 print "matched $&: ",pos $txt,"\n"; $txt =~ /\G\d/g; # 指针回到0,强制从0处开始匹配,数值1能匹配成功 print "matched $&: ",pos $txt,"\n";
如下是输出内容:
matched 12: 2 matched 34: 4 matched 34: matched 1: 1
若是将上面第三个匹配语句加上修饰符c,甚至后面的语句也都加上\G
和c修饰符,那么位移指针将卡在那个位置:
$txt="1234ab56"; $txt =~ /\d\d/g; print "matched $&: ",pos $txt,"\n"; $txt =~ /\d\d/g; print "matched $&: ",pos $txt,"\n"; $txt =~ /\G\d/gc; # 匹配失败,指针卡在原地 print "matched $&: ",pos $txt,"\n"; $txt =~ /\G\d/gc; # 匹配失败,指针继续卡在原地 print "matched $&: ",pos $txt,"\n"; $txt =~ /\G\d/gc; # 匹配失败,指针继续卡在原地 print "matched $&: ",pos $txt,"\n";
如下是输出结果:
matched 12: 2 matched 34: 4 matched 34: 4 matched 34: 4 matched 34: 4
通常来讲,全局匹配都会用循环去屡次迭代,和上面一次一次列出匹配表达式不同。因此,下面使用while循环的例子来对\G
和c修饰符稍做解释,其实理解了上面的内容,在循环中使用\G
和c修饰符也同样很容易理解。
$txt="1234ab56"; while($txt =~ m/\G\d\d/gc){ print "matched: $&, ",pos $txt,"\n"; }
执行结果:
matched: 12, 2 matched: 34, 4
当第三轮循环匹配到a字母的时候,因为使用了\G
,致使匹配失败,结束循环。
上面使用c与否是可有可无的,但若是这个while循环的后面后还有对$txt
的匹配,那么使用c修饰符与否就有关系了。例以下面两段程序,返回结果不同:
$txt="1234ab56"; while($txt =~ m/\G\d\d/gc){ # 使用c修饰符 print "matched: $&, ",pos $txt,"\n"; } $txt =~ m/\G\d\d/gc; print "matched: $&, ",pos $txt,"\n";
和
$txt="1234ab56"; while($txt =~ m/\G\d\d/g){ # 不使用c修饰符 print "matched: $&, ",pos $txt,"\n"; } $txt =~ m/\G\d\d/gc; print "matched: $&, ",pos $txt,"\n";
正则表达式通常都只用来匹配单行数据,但有时候却须要一次性匹配多行。好比匹配跨行单词、匹配跨行词组,匹配跨行的对称分隔符(如一对括号)。
使用m修饰符能够开启多行匹配模式。
例如:
$txt="ab\ncd"; $txt =~ /a.*\nc/m; print "===match start===\n$&\n===match end===\n";
执行,将输出:
===match start=== ab c ===match end===
关于多行匹配,须要注意的是元字符.
默认状况下没法匹配换行符。可使用[\d\D]
代替点,也能够开启s修饰符使得.
能匹配换行符。
例如,下面两个匹配输出的结果和上面是一致的。
$txt="ab\ncd"; $txt =~ /a.*c/ms; print "===match start===\n$&\n===match end===\n"; $txt="ab\ncd"; $txt =~ /a[\d\D]*c/m; print "===match start===\n$&\n===match end===\n";
默认状况下,.
元字符是不能匹配换行符\n
的,开启了s修饰符功能后,可让.
匹配换行符。正如刚才的那个例子:
$txt="ab\ncd"; $txt =~ /a.*c/m; # 匹配失败 print "===match start===\n$&\n===match end===\n"; $txt="ab\ncd"; $txt =~ /a.*c/ms; # 匹配成功 print "===match start===\n$&\n===match end===\n";
正则表达式最为人所抱怨的就是它的可读性极差,不管你的正则能力有多强,看着一大堆乱七八糟的符号组合在一块儿,都得一个符号一个符号地从左向右读。
万幸,perl正则支持表达式的分隔,甚至支持注释,只需加上x修饰符便可。这时候正则表达式中出现的全部空白符号都不会看成正则的匹配对象,而是直接被忽略。若是想要匹配空白符号,可使用\s
表示,或者将空格使用\Q...\E
包围。
例如,如下4个匹配操做是彻底等价的。
$ans="cat sheep tiger"; $ans =~ /(\w) *(\w) *(\w)/; # 正常状况下的匹配表达式 $ans =~ /(\w)\s* (\w)\s* (\w)/x; $ans = ~ / (\w)\s* # 能够加上本行注释:匹配第一个单词 (\w)\s* # 能够加上本行注释:匹配第二个单词 (\w) # 能够加上本行注释:匹配第三个单词 /x; $ans =~ / (\w)\Q \E # \Q \E强制将中间的空格看成字面符号被匹配 (\w)\Q \E (\w) /x;
对于稍微复杂一些的正则表达式,经常都会使用x修饰符来加强其可读性,最重要的是加上注释。这一点真的很是人性化。
前面说过,经过3个特殊变量$`
、$&
和$'
能够保存匹配内容以前的内容,匹配内容以及匹配内容以后的内容。可是,只要使用了这3个变量中的任何一个,后面的全部分组效率都会下降。perl提供了一个p修饰符,能实现彻底相同的功能:
${^PREMATCH} <=> $` ${^MATCH} <=> $& ${^POSTMATCH} <=> $'
一个例子便可描述:
$ans="cat sheep tiger"; $ans =~ /sheep/p; print "${^PREMATCH}\n"; # 输出"cat " print "${^MATCH}\n"; # 输出"sheep" print "${^POSTMATCH}\n"; # 输出" tiger"
在较老的perl版本中,若是使用同一个正则表达式作屡次匹配,正则引擎将只屡次编译正则表达式。不少时候正则表达式并不会改变,好比循环匹配文件中的行,这样的屡次编译致使性能降低很明显,因而可使用o修饰符让正则引擎对同一个正则表达式不重复编译。
在perl5.6中,默认状况下对同一正则表达式只编译一次,但一样能够指定o修饰符,使得即便正则表达式变化了也不要从新编译。
通常状况下,能够无视这个修饰符。
前文介绍的修饰符adluoimsxpngc
都是放在m//{FLAG}
的flag处的,放在这个位置会对整个正则表达式产生影响,因此它的做用范围有点广。
例如m/pattern1 pattern2/i
的i修饰符会影响pattern1和pattern2。
perl容许咱们定义只在必定范围内生效的修饰符,方式是(?imsx:pattern)
或(?-imsx:pattern)
或(?imsx-imsx:pattern)
,其中加上-
表示去除这个修饰符的影响。这里只列出了imsx,由于这几个最经常使用,其余的修饰符也同样有效。
例如,对于待匹配字符串"Hello world gaoxiaofang",使用如下几种模式去匹配的话:
/(?i:hello) world/
表示匹配hello时,可忽略大小写,但匹配world时仍然区分大小写。因此匹配成功
/(?ims:hello.)world/
表示能够跨行匹配helloworld,也能够匹配单行的hellosworld,且hello部分忽略大小写。因此匹配成功
/(?i:hello (?-i:world) gaoxiaoFANG)/
表示在第二个括号以前,可用忽略大小写进行匹配,但由于第二个括号里指明了去除i的影响,因此对world的匹配会区分大小写,可是对gaoxiaofang部分的匹配又不区分大小写。因此匹配成功
/(?i:hello (?-i:world) gaoxiao)FANG/
和前面的相似,可是将"FANG"放到了括号外,意味着这部分要区分大小写。因此匹配失败
1.锚定类的反斜线序列
所谓锚定,是指它匹配的是位置,而非字符,好比锚定行首的意思是匹配第一个字母前的空字符。也就是不少人说的"零宽断言(zero-width assertions)"。
\b
:匹配单词边界处的空字符\B
:匹配非单词边界处的空字符\<
:匹配单词开头处的空字符\>
:匹配单词结尾处的空字\A
:匹配绝对行首,换句话说,就是输入内容的开头\z
:匹配绝对行尾,换句话说,就是输入内容的绝对尾部\Z
:匹配绝对行尾或绝对行尾换行符前的位置,换句话说,就是输入内容的尾部\G
:强制从位移指针处进行匹配,详细内容见g和c修饰符以及\G主要解释下\A \z \Z
,其它的属于基础正则的内容,很少作解释了。
\A \z \Z
和^ $
的区别主要体如今多行模式下。在多行模式下:
$txt = "abcd\nABCD\n"; $txt1 = "abcd\nABCD"; $txt =~ /^ABC*/; # 没法匹配 $txt =~ /^ABC*/m; # 匹配 $txt =~ /\Aabc/; # 匹配 $txt =~ /\Aabc/m; # 匹配 $txt =~ /\AABC/m; # 没法匹配 $txt =~ /cd\n$/m; # 不匹配 $txt =~ /cd$\n/m; # 不匹配 $txt =~ /cd$/m; # 匹配 $txt =~ /CD\Z\n/m # 匹配 $txt =~ /CD\Z\n\Z/m; # 匹配 $txt =~ /CD\n\z/m; # 匹配 $txt1 =~ /CD\Z/m; # 匹配 $txt1 =~ /CD\z/m; # 匹配
从上面的$
匹配示例可知,$
表明的行尾,其实它在有换行符的时候匹配"\n",而不是"\n"的前、后,在没有换行符的时候,匹配行尾。
2.字符匹配反斜线序列
固然,除了如下这几种,还有\v \V \h \H \R \p \c \X
,这些基本不会用上,因此都不会在本文解释。
\w
:匹配单词构成部分,等价于[_[:alnum:]]
\W
:匹配非单词构成部分,等价于[^_[:alnum:]]
\s
:匹配空白字符,等价于[[:space:]]
\S
:匹配非空白字符,等价于[^[:space:]]
\d
:匹配数字,等价于[0-9]
\D
:匹配非数字,等价于[^0-9]
\N
:不匹配换行符,等价于[^\n]
。但\N{NAME}
有特殊意义,表示匹配已命名(名为NAME)的unicode字符序列,本文不介绍该特殊用法因为元字符.
默认没法匹配换行符,因此须要匹配换行符的时候,可使用特殊组合[\d\D]
或者(\n|\N)
来替换.
,换句话说,若是想匹配任意长度的任意字符,能够换成[\d\D]*
或者(\n|\N)*
,固然,前提是必须支持这3个反斜线序列。
之因此不用[\n\N]
替代元字符.
,是由于\N
有特殊意义,不能随意接符号和字母。
3.分组引用的反斜线序列
\1
:反向引用,其中1能够替换为任意一个正整数,即便超出9,例如\111
表示匹配第111个分组\g1
或\g{1}
:也是反向引用,只不过这种写法能够避免歧义,例如\g{1}11
表示匹配第一个分组内容后两个数字1\g{-1}
:还可使用负数,表示距离\g
左边的分组号,也就是相对距离。例如(abc)([a-z])\g{-1}
中的\g
引用的是[a-z]
,若是-1换成-2,则引用的abc
\g{name}
:引用已命名的分组(命名捕获),其中name为分组的名称\k<name>
:同上,引用已命名的分组(命名捕获),其中name为分组的名称\K
:不要将\K
左边的内容放进$&
。换句话说,\K
左边的内容即便匹配成功了,也会重置匹配的位置\1
表示引用第一个分组,\11
表示引用第11个分组,在基础正则中,是不支持引用超出9个分组的,但显然perl会将\11
的第二个1解析为引用,以便能引用更多分组。
同理\g1
和\g11
,只是使用\g
引用的方式能够加上大括号使引用变得更安全,更易读,且\g
可使用负数来表示从右向左相对引用。这样在\g{-2}
的左边添加新的分组括号时,无须修改引用表达式。
此处暂时还没介绍到命名分组,因此\g{name}
和\k<name>
留在后面再介绍。
\K
表示强制中断前面已完成的匹配。例如"abc22ABC" =~ /abc\K2.*/;
,虽然abc三个字母也被匹配,若是没有\K
,这3个字母将放进$&
中,可是\K
使得匹配完abc后当即切断前面的匹配,也就是从c字母后面开始从新匹配,因此这里匹配的结果是22ABC。
再例如,"abc123abcfoo"=~ /(abc)123\K\g1foo/;
,它匹配到123后被切断,可是分组引用还能够继续引用,因此匹配的结果是"abcfoo"。
在基础正则中,那些能匹配屡次的量词都会匹配最长内容。这种尽可能多匹配的行为称为"贪婪匹配"(greedy match)。
例如字符串"aa1122ccbb",用正则表达式a.*c
去匹配这个字符串,其中的.*
将直接从第二个字母a开始匹配到最结尾的b,由于从第二个字母a开始到最后一个字母b都符合.*
的匹配模式。再而后,去匹配字母c,但由于已经把全部字母匹配完了,只能回退一个字母一个字母地释放,每释放一个就匹配一次字母c,发现回退释放到倒数第三个字母就能知足匹配要求,因而这里的.*
最终匹配的内容是"a1122cc"。
上面涉及到回溯的概念,也就是将那些已经被量词匹配的内容回退释放。
上面描述的是贪婪匹配行为,还有非贪婪匹配、占有优先匹配,如下简单描述下他们的意义:
有必要搞清楚这几种匹配模式在匹配机制上的区别:
除了上面描述的*
量词会进行贪婪匹配,其余全部能进行屡次匹配的量词能够选择贪婪匹配模式、非贪婪匹配模式和占有优先匹配模式,只需选择对应的量词元字符便可。以下:
(量词后加上?) (量词后加上+) 贪婪匹配量词 非贪婪匹配量词 占有优先匹配量词 ----------------------------------------------------------------- * *? *+ ? ?? ?+ + +? ++ {M,} {M,}? {M,}+ {M,N} {M,N}? {M,N}+ {N} {N}? {N}+
几点须要说明:
{M,}?
和{M,N}?
,它们是等价的,由于最多只匹配M次{,N}
的模式,因此也没有对应的非贪婪和占有优先匹配模式{N}
这个量词,因为是精确匹配N次,因此贪婪与否对最终结果可有可无,可是却影响匹配时的行为:贪婪匹配最长,须要回溯,非贪婪匹配最短,不回溯,占有优先匹配最长不回溯。看如下示例便可理解贪婪和非贪婪匹配的行为:
$str="abc123abc1234"; # greedy match if( $str =~ /(a\w*3)/){ print "$&\n"; # abc123abc123 } # lazy match if( $str =~ /(a\w*?3)/){ print "$&\n"; # abc123 }
如下是占有优先匹配模式的示例:
$str="abc123abc1234"; if( $str =~ /a\w*+/){ # 成功 print "possessive1: $&\n"; } if( $str =~ /a\w*+3/){ # 失败 print "possesive2: $&\n"; }
因此,在使用占有优先匹配模式时,它后面不该该跟其余表达式,例如a*+x
永远匹配不了东西。绝大多数时候都是不会回溯的。可是少数状况下,它并不是强制锁住回溯,这个和正则引擎匹配原理有关,本文很少作解释。
另外,固化分组和占有优先并不彻底等价,它们只是匹配行为相同:匹配后不回溯。具体可对比后文对应内容。
在基础正则中,使用括号能够对匹配的内容进行分组,这种行为称为分组捕获。捕获后能够经过\1
这种反向引用方式去引用(访问)保存在分组中的匹配结果。
例如:
"abc11ddabc11" =~ /([a-z]*)([0-9]*)dd\1\2/;
在perl中,还可使用\gN
的方式来反向引用分组,这个在上一节"反斜线序列"中已经解释过了。例如,如下是和上面等价的几种写法:
"abc11ddabc11" =~ /([a-z]*)([0-9]*)dd\g1\g2/; "abc11ddabc11" =~ /([a-z]*)([0-9]*)dd\g{1}\g{2}/; "abc11ddabc11" =~ /([a-z]*)([0-9]*)dd\g{-2}\g{-1}/;
perl还会把分组的内容放进perl自带的特殊变量$1,$2,...,$N
中,它们和\1,\2,...\N
在匹配成功时的结果上没有区别,可是\N
这种类型的反向引用只在正则匹配中有效,正则匹配结束后就消亡了,而$N
由于是perl的变量,即便正则已经退出匹配,也依然能够引用。因此,咱们可使用$N
的方式来输出分组匹配的结果:
"abc11ddabc11" =~ /([a-z]*)([0-9]*)dd\1\2/; print "first group \\1: $1\n"; print "second group \\2: $2\n";
有两点须要注意:
\1
会在识别括号的时候重置,而$1仍保存上一次分组成功的值第一点,示例可知:
"abcde" =~ /([0-9]*)de/; print "null group: $1\n";
第二点,从机制上去分析。\1
是每一个正则匹配都相互独立的,而$1
则保存分组捕获成功的值,即便此次值是上次捕获的。
这里稍微解释下正则匹配关于分组捕获的匹配过程:
例如,匹配表达式"12abc22abc" =~ /\d(abc)\d\d\1/;
,当正则引擎去匹配数据时:
1.首先匹配第一个数字1,发现符合\d
,因而继续用(abc)
去匹配字符串,由于发现了是分组括号,因而会将第二个字符2放进分组,发现不匹配字母a,因而匹配失败,丢弃这个分组中的内容。
2.正则引擎继续向后匹配数值2,发现符合\d
,因而用(abc)
去匹配字符串,接着会将第三个字符a放进分组,发现能匹配,继续匹配字符串中的b、c发现都能匹配,因而分组捕获完成,将其赋值给$1
,以后就能用\1
和$1
去引用这个分组的内容。
3.后面继续去匹配\d\d\1
,直到匹配结束。
固然,具体匹配的过程不会真的这么简单,它会有一些优化匹配方式,以上只是用逻辑去描述匹配的过程。
在perl中,支持的分组捕获更强大、更完整,它除了支持普通分组(也就是直接用括号的分组),还支持:
(?<NAME>...)
:捕获后放进一个已分配好名称(即NAME)的分组中,之后可使用这个名称来引用这个分组,如\g{NAME}
引用(?:...)
:仅分组,不捕获,因此后面没法再引用这个捕获(?>...)
:一匹配成功就永不交回内容(用回溯的想法理解很容易)匿名捕获是指仅分组,不捕获。由于不捕获,因此没法使用反向引用,也不会将分组结果赋值给$1
这种特殊变量。
虽然有了分组捕获功能,就能够实现任何需求,但有时候可让这种行为变得更人性化,减小维护力度。
例如字符串"xiaofang or longshuai",使用模式/(\w+) or (\w+)/
去捕获,用$1
和$2
分别引用or左右两个单词:
$str = "xiaofang or longshuai"; if ($str =~ /(\w+) or (\w+)/){ print "name1: $1, name2: $2\n"; }
但若是需求是中间的关系or也能够换成and,为了同时知足and和or两种需求,使用模式/(\w+) (and|or) (\w+)/
去匹配,可是这时引用的序号就得由$2
变为$3
:
$str = "xiaofang or longshuai"; if ($str =~ /(\w+) (or|and) (\w+)/){ print "name1: $1, name2: $3\n"; }
若是使用匿名捕获,对and和or这样可有可无,却有可能改变匹配行为的内容,能够将其放进一个无关的分组中。这样不会对原有的其他正则表达式产生任何影响:
$str = "xiaofang or longshuai"; if ($str =~ /(\w+) (?:or|and) (\w+)/){ print "name1: $1, name2: $2\n"; }
注意上面仍然使用$2
引用第三个括号。
一样,若是要在正则内部使用反向引用,也同样使用\2
来引用第三个括号。
另外,在前文还介绍过一个n
修饰符,它也表示非捕获仅分组行为。但它只对普通分组有效,对命名分组无效。且由于它是修饰符,它会使全部的普通分组都变成非捕获模式。
$str = "xiaofang or longshuai"; if ($str =~ /(\w+) (or|and) (\w+)/n){ print "name1: $1, name2: $2\n"; }
因为上面开启了n修饰符,使得3个普通分组括号都变成非捕获仅分组行为,因此\1
和$1
都没法使用。除非正则中使用了命名分组。
命名捕获是指将捕获到的内容放进分组,这个分组是有名称的分组,因此后面可使用分组名去引用已捕获进这个分组的内容。除此以外,和普通分组并没有区别。
当要进行命名捕获时,使用(?<NAME>)
的方式替代之前的分组括号()
便可。例如,要匹配abc并将其分组,之前普通分组的方式是(abc)
,若是将其放进命名为name1的分组中:(?<name1>abc)
。
当使用命名捕获的时候,要在正则内部引用这个命名捕获,除了可使用序号类的绝对引用(如\1
或\g1
或\g{1}
),还可使用如下任意一种按名称引用方式:
\g{NAME}
\k{NAME}
\k<NAME>
\k'NAME'
若是要在正则外部引用这个命名捕获,除了可使用序号类的绝对应用(如$1
),还可使用$+{NAME}
的方式。
实际上,后一种引用方式的本质是perl将命名捕获的内容放进了一个名为%+
的特殊hash类型中,因此可使用$+{NAME}
的方式引用,若是你不知道这一点,那就无视与此相关的内容便可,不过都很简单,一看就懂。
例如:
$str = "ma xiaofang or ma longshuai"; if ($str =~ / (?<firstname>\w+)\s # firstname -> ma (?<name1>\w+)\s # name1 -> xiaofang (?:or|and)\s # group only, no capture \g1\s # \g1 -> ma (?<name2>\w+) # name2 -> longshuai /x){ print "$1\n"; print "$2\n"; print "$3\n"; # 或者指定名称来引用 print "$+{firstname}\n$+{name1}\n$+{name2}\n"; }
其中上述代码中的\g1
还能够替换为\1
、\g{firstname}
、\k{firstname}
或\k<firstname>
。
经过使用命名捕获,能够无视序号,直接使用名称便可准确引用。
首先固化分组不是一种分组,因此没法去引用它。它和"占有优先"匹配模式(贪婪匹配、惰性匹配、占有优先匹配三种匹配模式,见后文)是等价的除了这两种称呼,在不一样的书、不一样的语言里还有一种称呼:原子匹配。
它的表示形式相似于分组(?>)
,因此有些地方将其称呼为"固化分组"。再次说明,固化分组不是分组,没法进行引用。若是非要将其看做是分组,能够将其理解为被限定的匿名分组:不捕获,只分组。
若是不知道什么是回溯,看完下面的例子就明白。
例如"hello world"能够被hel.* world
成功匹配,但不能被hel(?>.*) world
匹配。由于正常状况下,.*
匹配到全部内容,而后往回释放已匹配的内容直到释放完空格为止,这种往回释放字符的行为在正则术语中称为"回溯"。而固化分组后,.*
已匹配后面全部内容,这些内容一经匹配毫不交回,即没法回溯。
可是,若是正则表达式是hel(?>.* world)
,即将原来分组外面的内容放进了分组内部,这时在分组内部是会回溯的,也就是说能匹配"hello world"。
$str="ma longshuai gao xiaofang"; if($str =~ /ma (?>long.*)/){ # 成功 print "matched\n"; } if($str =~ /ma (?>long.*)gao/){ # 失败 print "matched\n"; } if($str =~ /ma (?>long.*gao)/){ # 成功 print "matched\n"; } if($str =~ /ma (?>long.*g)ao/){ # 成功 print "matched\n"; }
固化分组看上去挺简单的,此处也仅介绍了它最简单的形式。但实际上固化分组很复杂,它涉及了很是复杂的正则引擎匹配原理和回溯机制。若是有兴趣,能够阅读《精通正则表达式》一书的第四章。
"环视"锚定,即lookaround anchor,也称为"零宽断言",它表示匹配的是位置,不是字符。
(?=...)
:表示从左向右的顺序环视。例如(?=\d)
表示当前字符的右边是一个数字时就知足条件(?!...)
:表示顺序环视的取反。如(?!\d)
表示当前字符的右边不是一个数字时就知足条件(?<=...)
:表示从右向左的逆序环视。例如(?<=\d)
表示当前字符的左边是一个数字时就知足条件(?<!)...
:表示逆序环视的取反。如(?<!\d)
表示当前字符的左边不是一个数字时就知足条件关于"环视"锚定,最须要注意的一点是匹配的结果不占用任何字符,它仅仅只是锚定位置。
例如"your name is longshuai MA"和"your name is longfei MA"。使用(?=longshuai)
将能锚定第一个句子中单词"longshuai"前面的空字符,但它的匹配结果是"longshuai"前的空白字符,因此(?=longshuai)long
才能表明"long"这几个字符串,因此仅对于此处的两个句子,long(?=shuai)
和(?=longshuai)long
是等价的。
通常为了方便理解,在顺序环视的时候会将匹配内容放在锚定括号的左边(如long(?=longshuai)
),在逆序环视的时候会将匹配的内容放在锚定括号的右边(如(?<=long)shuai
)。
另外,不管是哪一种锚定,都是从左向右匹配再作回溯的(假设容许回溯),即便是逆序环视。
例如:
$str="abc123abcc12c34"; # 顺序环视 $str =~ /a.*c(?=\d)/; # abc123abcc12c print "$&\n"; # 顺序否认环视 $str =~ /a.*c(?!\d)/; # abc123abc print "$&\n"; # 逆序环视,这里能逆序匹配成功,靠的是锚定括号后面的c $str =~ /a.*(?<=\d)c/; # abc123abcc12c print "$&\n"; # 逆序否认环视 $str =~ /a.*(?<!\d)c/; # abc123abcc print "$&\n";
逆序环视的表达式必须只能表示固定长度的字符串。例如(?<=word)
或(?<=word|word)
能够,但(?<=word?)
不能够,由于?
匹配0或1长度,长度不定,它没法对左边是word仍是wordx作正确判断。
$str="hello worlds Gaoxiaofang"; $str =~ /he.*(?<=worlds?) Gao/; # 报错 $str =~ /he.*(?<=worlds|world) Gao/; # 报错
在PCRE中,这种变长的逆序环视锚定可重写为(?<=word|words)
,但perl中不容许,由于perl严格要求长度必须固定。
perl中的\Q...\E
用来强制包围一段字符,使得里面的正则符号都当作普通字符,不会有特殊意义,它是一种很是强的引用。但注意,它没法强制变量的替换。
例如:
$sub="world"; $str="hello worlds gaoxiaofang"; $str =~ /\Q$sub\E/; # $sub会替换,因此匹配成功world $str =~ /\Q$sub.\E/; # 元字符"."被当作普通的字符,因此没法匹配
由于能够在正则模式中使用变量替换,因此咱们能够将正则中的一部分表达式事先保存在变量中。例如:
$str="hello worlds gaoxiaofang"; $pattern="w.*d"; $str =~ /$pattern/; print "$&\n";
可是,这样缺陷很大,在保存正则表达式的变量中存放的特殊字符要防止有特殊意义。例如,当使用m//
的方式作匹配分隔符时,不能在变量中保存/
,除非转义。
perl提供了qr/pattern/
的功能,它把pattern部分构建成一个正则表达式对象,而后就能够在正则表达式中直接引用这个对象,更方便的是能够将这个对象保存到变量中,经过引用变量的方式来引用这个已保存好的正则对象。
$str="hello worlds gaoxiaofang"; # 直接做为正则表达式 $str =~ qr/w.*d/; print "$&\n"; # 保存为变量,再做为正则表达式 $pattern=qr/w.*d/; $str =~ $pattern; # (1) $str =~ /$pattern/; # (2) print "$&\n"; # 保存为变量,做为正则表达式的一部分 $pattern=qr/w.*d/; $str =~ /hel.* $pattern/; print "$&\n";
还容许为这个正则对象设置修饰符,好比忽略大小写的匹配修饰符为i,这样在真正匹配的时候,就只有这一部分正则对象会忽略大小写,其他部分仍然区分大小写。
$str="HELLO wORLDs gaoxiaofang"; $pattern=qr/w.*d/i; # 忽略大小写 $str =~ /HEL.* $pattern/; # 匹配成功,$pattern部分忽略大小写 $str =~ /hel.* $pattern/; # 匹配失败 $str =~ /hel.* $pattern/i; # 匹配成功,全部都忽略大小写