Perl中带有/g修饰符的正则表达式匹配为何仅能成功一次?
Perl中带有/g修饰符的正则表达式匹配为何仅能成功一次?
这其实是Perl里/g修饰符的一个经典“坑”,很多刚接触全局匹配的朋友都会懵,我来给你拆解清楚~
先看你给出的代码例子:
# perl 5.26.1 $foo = "batcathat"; if ($foo =~ /cat/g) { print "yes\n"; } else { print "no\n"; } if ($foo =~ /cat/g) { print "yes\n"; } else { print "no\n"; }
你预期输出两次yes,但实际是yes和no,核心原因就是/g修饰符的位置记忆特性:
当你使用/g进行匹配时,Perl会给目标字符串维护一个叫pos的“指针”,记录当前匹配的起始位置:
- 第一次匹配
/cat/g时,指针从字符串开头(位置0)开始,找到了bat后面的cat,匹配成功后,指针会跳到cat结束的位置(对应字符串里hat的开头); - 第二次匹配时,Perl直接从这个指针位置开始找,后面的字符串是
hat,根本找不到cat,所以匹配失败,输出no; - 有趣的是,如果这时候你再做第三次匹配,会发现又能成功——因为匹配失败后,Perl会自动把
pos指针重置回开头,下次匹配又从头开始了。
那为什么会有这种设计呢?其实/g的初衷就是用来做全局遍历匹配的,比如你看到的这段代码:
while ( $foo =~ /pattern/g) { $some_incrementing_var++ };
一开始看起来像无限循环,但实际上每次匹配成功后,pos指针都会往后挪,直到某次找不到匹配,循环条件为假,自然就终止了——这个逻辑刚好能用来统计字符串里pattern出现的次数,或者逐个处理每个匹配结果。
如果想要让每次匹配都从头开始,不用管之前的匹配位置,有两种简单办法:
- 去掉
/g修饰符:这样每次匹配都会从字符串开头开始,不会记录pos指针; - 手动重置
pos指针:在每次匹配前,用pos($foo) = 0;把指针拉回开头,比如修改你的代码:
$foo = "batcathat"; if ($foo =~ /cat/g) { print "yes\n"; } pos($foo) = 0; # 重置匹配位置 if ($foo =~ /cat/g) { print "yes\n"; }
这样输出就会是你预期的两次yes了。
最后补充个小知识点:/g在列表上下文和标量上下文的行为不一样——比如my @all_matches = $foo =~ /cat/g;,这时候会一次性返回所有匹配到的结果,同时pos指针会直接跳到字符串末尾;而在if、while这种标量上下文里,就是逐个推进指针的匹配方式。
备注:内容来源于stack exchange,提问作者Lou
相关产品推荐
相关产品推荐

