分组捕获和分组引用
在基础正则中,使用括号可以对匹配的内容进行分组,这种行为称为分组捕获。捕获后可以通过\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/;
say "first group: $1";
say "second group: $2";
有几点需要注意:
- 分组可能未捕获任何内容(比如那些允许匹配0次的量词),但整个匹配是成功的。这时
$N
引用分组时,得到的结果将是空串 - 每次正则匹配都重置
$N
,因此$N
在下一次正则匹配之前一直有效
例如:
# 分组没有捕获内容,但匹配是成功的,$1是空串
"abcde" =~ /([0-9]*)de/;
say "empty group: $1";
# 分组捕获到数值4,$1保存了4,
# 但下次正则匹配后,将重置$1
"abc4de" =~ /([0-9]*)de/;
say "group: $1"; # 4
"abcde" =~ /([0-9]*)de/;
say "empty group: $1"; # 空串
Perl更强大的分组捕获
Perl正则除了支持普通分组(也就是直接用括号的分组),还支持:
- 命名分组
(?<NAME>...)
:捕获后放进一个名为NAME的分组,正则表达式中可使用该名称来反向引用该分组内容,如\g{NAME}
- 匿名分组
(?:...)
:仅分组,不捕获,所以后面无法再引用这个捕获 - 固化分组
(?>...)
:一匹配成功就永不交还内容
匿名分组
匿名捕获是指仅分组,不捕获。因为不捕获,所以无法使用反向引用,也不会将分组结果赋值给$1
这种特殊变量。
例如:
my $str = "xiaofang or longshuai";
if ($str =~ /(\w+) or (\w+)/){
say "name1: $1, name2: $2";
}
但如果中间的or也可以换成and,为了同时满足and和or两种需求,使用模式/(\w+) (and|or) (\w+)/
去匹配,但这时引用的序号就得由$2
变为$3
:
my $str = "xiaofang or longshuai";
if ($str =~ /(\w+) (or|and) (\w+)/){
say "name1: $1, name2: $3";
}
可使用匿名分组解决这样的问题:
my $str = "xiaofang or longshuai";
if ($str =~ /(\w+) (?:or|and) (\w+)/){
say "name1: $1, name2: $2";
}
另外,Perl有一个n
修饰符,它使得普通分组变成匿名分组,但对命名分组无效。且因为它是修饰符,它会使修饰范围内的所有普通分组都变成匿名分组。
my $str = "xiaofang or longshuai";
# 等价于:
# if ($str =~ /(?:\w+) (?:or|and) (?:\w+)/n){
if ($str =~ /(\w+) (or|and) (\w+)/n){
say "name1: $1, name2: $2"; # 错,$1 $2不可用
}
命名分组
命名分组是指将捕获到的内容放进分组,这个分组是有名称的分组,后面可以使用分组名去引用已捕获进这个分组的内容。除此之外,和普通分组并无区别。
当要进行命名分组时,使用(?<NAME>)
的方式替代普通分组的括号()
即可。例如,要匹配abc并将其分组,以前普通分组的方式是(abc)
,如果将其放进命名为name1的分组中:(?<name1>abc)
。
当使用命名捕获的时候,要在正则内部引用这个命名捕获,除了可以使用序号类的绝对引用(如\1
或\g1
或\g{1}
),还可以使用以下任意一种按名称的引用方式:\g{NAME} \k{NAME} \k<NAME> \k'NAME'
。
如果要在正则外部引用这个命名捕获,除了可以使用序号类的绝对应用(如$1
),还可以使用$+{NAME}
的方式。可使用后一种引用方式的原因是,Perl将命名捕获的内容放进了一个名为%+
的特殊hash中。
例如:
my $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){
say "$1";
say "$2";
say "$3 ";
# 或者指定名称来引用
say "$+{firstname}\n$+{name1}\n$+{name2}";
}
固化分组
固化分组的用法是(?>PATTERN)
,它会将PATTERN匹配的内容吞掉且绝不交还,相当于是将其匹配的内容进行固化。它和占有优先匹配模式在行为上是类似的:占有之后不回溯。
另外,固化分组不是一种分组,因此无法去引用它吞掉的内容。
例如"hello world"
可以被hel.* world
成功匹配,但不能被hel(?>.*) world
匹配。因为固化分组时,.*
匹配后面所有内容并将其吞掉,这些内容一经匹配绝不交回。
但如果正则表达式是hel(?>.* world)
将能匹配成功,即将原来分组外面的内容放进了分组内部。固化分组的内部是允许回溯的。
再例如:
my $str="ma longshuai gao xiaofang";
if($str =~ /ma (?>long.*)/){ # 成功
say "matched";
}
if($str =~ /ma (?>long.*)gao/){ # 失败
say "matched";
}
if($str =~ /ma (?>long.*gao)/){ # 成功
say "matche";
}
if($str =~ /ma (?>long.*g)ao/){ # 成功
say "matched";
}