使用Perl正则进行匹配

在Perl中使用正则表达式进行匹配是非常简单的一件事。例如:

"abc123def" =~ /\d+/;

上面示例会使用正则表达式\d+去匹配字符串abc123def。其中=~是正则匹配操作符,左边是待匹配的字符串数据,右边是双斜线包围的正则表达式。

Perl中,正则匹配之后,默认不会返回所匹配成功的内容,而是返回一个代表着本次正则匹配是否成功的数据,因此正则匹配可直接用来做条件判断。实际上,Perl正则匹配的返回值问题比较复杂,稍后会详细解释。

my @names = qw(junma jinlong tuner fairy wugui);
my @names1;
for(@names){
  push $_,@names1 if($_ =~ /[u-z]/);
}
say "@names1";

当正则表达式要匹配的是变量$_,则可以直接简写为正则表达式。也就是说,$_ =~ /reg//reg/是等价的。

因此,上面筛选出包含uvwxyz字符的名字的示例,等价于如下简短代码:

my @names = qw(junma jinlong tuner fairy wugui);
my @names1 = grep {/[u-z]/} @names;
say "@names1";

严格来说,Perl中正则表达式的书写方式为m//,其中斜线可以替换为其它符号,规则如下:

  • 双斜线可以替换为任意其它成对符号,例如可以是对称的各种括号m() m{},也可以是相同的字符m!! m%%
  • 当采用双斜线时,可省略前缀m字母,即//等价于m//
  • 如果正则表达式中出现了和分隔符相同的字符,需转义表达式中的符号,但建议换分隔符,例如/http:\/\//转换成m%http://%

使用qr创建正则表达式

Perl中除了可以将正则写为m/reg/或省略m的/reg/,还可以通过qr来构建正则表达式。

qr和q、qq、qw类似,只不过它构建的是正则表达式的字面量。例如:

qr/abc.*def/
qr(abc.*def)
qr{ab.*def}

此外,可以在正则模式中使用变量替换,所以可以将正则的一部分表达式事先保存在变量中。例如:

$str="hello worlds junmajinlong";
$pattern="w.*d";
$str =~ /$pattern/;

但这样做缺陷很大,必须要确保插入在正则表达式中的变量pattern中没有存放具有特殊意义的字符。例如,当使用m//格式的正则进行匹配时,不能在变量中保存/,除非转义。

Perl提供了qr/pattern/的功能,它把pattern部分构建成一个正则表达式对象,然后就可以在正则表达式中直接引用这个对象,更方便的是可以将这个对象保存到变量中,通过引用变量的方式来引用这个已保存好的正则对象。

$str="hello worlds junmajinlong";

# 直接作为正则表达式
$str =~ qr/w.*d/;

# 保存为变量,再作为正则表达式
$pattern=qr/w.*d/;
$str =~ $pattern;    # (1)
$str =~ /$pattern/;  # (2)

# 保存为变量,作为正则表达式的一部分
$pattern=qr/w.*d/;
$str =~ /hel.* $pattern/;

还允许为这个正则对象设置修饰符,比如忽略大小写的匹配修饰符为i,这样在真正匹配的时候,就只有这一部分正则对象会忽略大小写,其余部分仍然区分大小写。

$str="HELLO wORLDs gaoxiaofang";

$pattern=qr/w.*d/i;         # 忽略大小写

$str =~ /HEL.* $pattern/;   # 匹配成功,$pattern部分忽略大小写
$str =~ /hel.* $pattern/;   # 匹配失败
$str =~ /hel.* $pattern/i;  # 匹配成功,所有都忽略大小写

小心正则表达式中的特殊符号

正则表达式中使用了很多元字符,这些元字符在正则表达式中有特殊含义。另外,Perl自身也使用了很多特殊符号。如果在正则表达式中使用了两者冲突的特殊符号,就需要特殊处理。

例如,可以在正则表达式中内插变量:

$str =~ /hel.*$pattern/;

但如果是下面这样的正则表达式呢?

$str =~ /abc$\ndef/m;

这里有两种解析方式:

  • $\看作变量,等价于/abc${\}ndef/m
  • 在多行模式下,匹配abc以及下一行def

Perl会默认解析为前者,如果确实想要的是前者,在正则表达式中内插变量,都建议使用${var}的变量引用方式。

如果想要的结果是后者,则可改写为:

$str =~ /abc$(?:)\ndef/m;

即,使用一个空的非捕获分组括号将$符号和\隔开。

更好的方式是将正则表达式部分以单引号的方式定义为变量,然后在正则表达式中内插该变量:

my $re_str = q%abc$\ndef%;
$str =~ /${re_str}/m;

有时候并不是想要将Perl中的特殊字符转义,而是想要将正则表达式中的特殊字符转义。此时可使用反斜线转义元字符,使其被当作普通字面符号。如果要转义的元字符较多,可使用Perl提供的\Q...\E进行强转。但注意,\Q仍然无法转义变量内插。

$sub="world";
$str="hello worlds junmajinlong";
$str =~ /\Q$sub\E/;  # $sub会替换,所以匹配成功world
$str =~ /\Q$sub.\E/; # 元字符"."被当做普通的点符号,所以无法匹配