Perl正则模式修饰符

指定模式匹配的修饰符,可以改变正则表达式的匹配行为。例如,下面的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}中,它们在结果上对应$` $& $'
  • aul:分别表示用ASCII、Unicode和Locale的方式来解释正则表达式
  • d:使用unicode或原生字符集,就像5.12和之前那样,不用考虑这个修饰符

这些修饰符可以连用,连用时顺序可随意。例如下面两行是等价的行为:全局忽略大小写的匹配行为。

m/REG/ig
m/REG/gi

上面的修饰符,本节介绍imsxpo这几个修饰符,gc在后面介绍全局匹配时解释,n修饰符在后面分组捕获的地方解释,auld修饰符和字符集相关,不打算解释。

i修饰符:忽略大小写

该修饰符使得正则匹配的时候,忽略大小写。

my $name="aAbBcC";
if($name =~ m/ab/i){
    print "pre match: $` \n";     # 输出a
    print "match: $& \n";         # 输出Ab
    print "post match: $' \n";    # 输出BcC
}

m修饰符:多行匹配模式

正则表达式一般都只用来匹配单行数据,但有时候却需要一次性匹配多行。比如匹配跨行单词、匹配跨行词组,匹配跨行的对称分隔符(如一对跨行括号)。

使用m修饰符可以开启多行匹配模式。

例如:

my $txt="ab\ncd";
$txt =~ /a.*\nc/m;
say "===match start==="
say $&;
say "===match end===";

执行,将输出:

===match start===
ab
c
===match end===

关于多行匹配,需要注意的是元字符.默认情况下无法匹配换行符。可以使用[\d\D]代替点,也可以开启s修饰符使.能匹配换行符。

例如,下面两个匹配的结果和上面是一致的。

$txt =~ /a.*c/ms;
$txt =~ /a[\d\D]*c/m;

s修饰符

默认情况下,.元字符不能匹配换行符\n,开启了s修饰符功能后,可以让.匹配换行符。正如刚才的那个例子:

my $txt="ab\ncd";
$txt =~ /a.*c/m;        # 匹配失败
$txt =~ /a.*c/ms;       # 匹配成功

x修饰符

正则表达式最为人所抱怨的就是它的可读性极差,无论你的正则能力有多强,看着一大堆乱七八糟的符号组合在一起,都得一个符号一个符号地从左向右读。

万幸,Perl正则允许分隔表达式,甚至支持注释,只需加上x修饰符即可。这时候正则表达式中出现的所有空白符号都不会当作正则的匹配对象,而是直接被忽略。如果想要匹配空白符号,可以使用\s表示,或者将空格使用\Q...\E包围。

例如,以下4个匹配操作是完全等价的。

my $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修饰符来增强其可读性,最重要的是加上注释。

p修饰符

在Perl v5.20版本之前,通过3个特殊变量$` $& $'保存正则匹配相关的内容时,可能会因为频繁地拷贝字符串使得效率低下。Perl为此提供了一个p修饰符,使得能够使用另外三个等价但不降低性能的变量:

${^PREMATCH}    <=>   $`
${^MATCH}       <=>   $&
${^POSTMATCH}   <=>   $'

在Perl v5.20中及之后的版本,Perl采取了写时复制的技术,使得使用这三个变量的过程中不会降低性能。

o修饰符

正则表达式要能够去匹配字符串,大致需要经过几个过程:

  • 正则表达式文本分析
  • 编译正则
  • 正则引擎使用编译后的正则去匹配字符串

很多时候的正则表达式是固定不变的,比如通过一个正则表达式去循环匹配文件中的行,在循环过程中这个正则不会发生变化。

while(){
  print if /^abc\d+$/;
}

为了优化,Perl不会每次都去执行耗时的编译过程,只会在第一次正则匹配的过程中去编译该正则文本,然后将编译后的正则缓存下来,下次将直接使用该正则去执行匹配操作。

在以前古老的Perl版本(Perl v5.6及以前)中,Perl不会自动优化为只编译一次正则表达式,而是每次使用正则匹配的时候都会编译。但是现在,Perl在绝大多数情况下,都会自动优化正则的编译过程。

但某些场景下,正则表达式不是固定不变的,比如正则表达式中使用了变量内插。Perl认为在正则表达式中使用变量,意味着正则表达式可能会是动态的,会发生改变,因此Perl不会去优化这种形式的正则表达式。

while(){
  print if /^abc${reg}\d+$/;
}

但如果能够确保正则表达式中的变量不会在未来发生改变,那么可以为该正则表达式加上o修饰符,这将会强制只编译一次正则,并且以后都使用第一次编译后缓存下来的正则。

使用o修饰符的时候,一定要确保内插的变量不会发生改变,否则只编译一次的正则可能会不符合预期。例如:

my $day=qw(Mon Tue Wed Thr Fri Sat Sun)[rand 7];

while(...){
  print if /^${day}/o;
}

这里加上修饰符o,将使得变量$day的随机效果失效,在整个while循环过程中,都将使用相同的正则表达式去匹配$_

范围模式匹配修饰符(?imsx-imsx:pattern)

前文介绍的正则修饰符都是放在m//{FLAG}的FLAG处的,放在这个位置会对整个正则表达式产生影响,所以它的作用范围较宽。例如,m/pattern1 pattern2/i的i修饰符会影响pattern1和pattern2。

Perl允许指定只在一定范围内生效的修饰符,方式是(?imsx:pattern)(?-imsx:pattern)(?imsx-imsx:pattern),其中加上-表示去除这个修饰符的影响。这里只列出了imsx,因为这几个最常用,其他的修饰符也一样有效。

例如,对于待匹配字符串"Hello world junmajinlong",使用以下几种模式去匹配的话:

  • /(?i:hello) world/
    表示匹配hello时,可忽略大小写,但匹配world时仍然区分大小写。所以匹配成功
  • /(?ims:hello.)world/
    表示可以跨行匹配helloworld,也可以匹配单行的hellosworld,且hello部分忽略大小写。所以匹配成功
  • /(?i:hello (?-i:world) junmajinLONG)/
    表示在第二个括号之前,可忽略大小写进行匹配,但第二个括号里指明了去除i的影响,所以对world的匹配会区分大小写,但是对junmajinlong部分的匹配又不区分大小写。所以匹配成功
  • /(?i:hello (?-i:world) junmajin)LONG/ 和前面的类似,但是将LONG放到了括号外,意味着这部分要区分大小写。所以匹配失败