为什么 =~ 只评估一次?

Lou*_*Lou 6 regex perl

在此示例脚本中:

#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";
}
Run Code Online (Sandbox Code Playgroud)

这将打印:

yes
no
Run Code Online (Sandbox Code Playgroud)

预期输出是:

yes
yes
Run Code Online (Sandbox Code Playgroud)

我可以通过打印字符串来确认它没有通过运行正则表达式匹配而发生变异。

为什么 Perl 看起来只对正则表达式求值一次?我在谷歌或手册上找不到任何关于此的信息,而且这种行为对我来说并不直观。我希望每次评估正则表达式匹配时,它都会从新开始,并且不会记住有关上一场比赛的任何信息。

编辑:对于未来的上下文,在我发现一些如下所示的代码后提出了这个问题:

while ( $foo =~ /pattern/g) { $some_incrementing_var++ };
Run Code Online (Sandbox Code Playgroud)

我最初不明白这个 while 循环如何终止,因为乍一看它看起来像一个无限循环。

ike*_*ami 6

//g在标量上下文中开始匹配上一个 /g 匹配结束的位置。由于cat只出现一次,因此第二次返回 false 表示不匹配。

这在循环中很有用:

while ( /\w+/g ) {
   say $&;
}
Run Code Online (Sandbox Code Playgroud)

我们可以使用posto 来查看发生了什么。

while ( /\w+/g ) {
   say $&;
}
Run Code Online (Sandbox Code Playgroud)
local $_ = "abc def ghi";
while ( ( say pos // 0 ), /\w+/g ) {
   say $&;
}
Run Code Online (Sandbox Code Playgroud)

但是,虽然这在循环中很有用,但它没有任何意义if ( //g )(除非您展开循环)。这意味着什么?“检查是否匹配,并无缘无故地继续检查更多匹配”???显然,这是没有意义的。取出g打印yes两次。


bri*_*foy 4

标量上下文/g是我最喜欢的工具之一,它在perlop中的记录非常长。对于它的文档来说,这似乎是一个奇怪的地方,但是有些标志会影响匹配运算符的工作方式,并且有些标志会影响模式的工作方式(请参阅了解正则表达式和匹配运算符标志之间的区别)。

@ikegami 提到了,您可以在perlfuncpos中阅读有关内容。Perl 跟踪它在字符串中的位置。如果不在标量上下文中,则匹配运算符从字符串的开头开始并向结尾移动。匹配后就完成了。下一个匹配再次从字符串的开头开始。/g

这略有变化\g。第一个匹配从字符串的开头开始,匹配(如果可以的话),并设置匹配结束后的位置。这就是pos. 下一次\g,比赛开始于pos。就你而言,

在列表上下文中,它做同样的事情,但会耗尽。它进行第一场比赛,然后从该位置开始尝试下一场比赛。这就是列表上下文中的匹配无法找到重叠匹配的原因:它在重叠开始的位置之后开始匹配。Jeffrey Friedl 的《掌握正则表达式》尽管已经很老了,但它很好地探讨了正则表达式的工作原理、它们的不同工作方式以及各种方式如何排除其他方式可能具有的功能。

use v5.26;

my $foo = "batcathat";

say "pos is ", pos($foo) // 0; # not started, so undef

if ($foo =~ /cat/g) {
    print "yes\n";
} else {
    print "no\n";
}

say "pos is ", pos($foo) // 0;  # now is 6

if ($foo =~ /cat/g) {
    print "yes\n";
} else {
    print "no\n";
}
Run Code Online (Sandbox Code Playgroud)

这输出:

pos is 0
yes
pos is 6
no
Run Code Online (Sandbox Code Playgroud)

该位置按字符串进行跟踪,并在匹配失败后重置\g(就像其他正则表达式副作用变量一样):

use v5.26;

my $foo = "batcathat";

say "pos is ", pos($foo) // 0; # not started, so undef

if ($foo =~ /cat/g) {
    print "yes\n";
} else {
    print "no\n";
}

$foo =~ /dog/g;

say "pos is ", pos($foo) // 0;  # now is 0 again after failed match

if ($foo =~ /cat/g) {
    print "yes\n";
} else {
    print "no\n";
}
Run Code Online (Sandbox Code Playgroud)

输出显示两次尝试寻找cat工作,因为dog尝试失败并重置pos

pos is 0
yes
pos is 0
yes
Run Code Online (Sandbox Code Playgroud)

但是,也有一种方法可以解决这个问题。该/c标志告诉匹配运算符pos在失败时不要重置:

use v5.26;

my $foo = "batcathat";

say "pos is ", pos($foo) // 0; # not started, so undef

if ($foo =~ /cat/g) {
    print "yes\n";
} else {
    print "no\n";
}

$foo =~ /dog/gc; # will not reset pos

say "pos is ", pos($foo) // 0;  # now is 6 because /c

if ($foo =~ /cat/g) {
    print "yes\n";
} else {
    print "no\n";
}
Run Code Online (Sandbox Code Playgroud)

现在您回到了原始输出:

pos is 0
yes
pos is 6
no
Run Code Online (Sandbox Code Playgroud)

这允许您执行像这个非常简单的示例这样的操作。您可以匹配某个内容,如果不起作用,请尝试其他内容,而不会丢失您在字符串中的位置:

use v5.26;

my $foo = "batcathat";

$foo =~ /bat/g;

if( $foo =~ /\Gmat/gc ) {
    do_mat_things();
} elsif( $foo =~ /\Gchat/gc ) {
    do_chat_things();
} elsif( $foo =~ /\Gcat/gc ) {
    do_cat_things();
}
Run Code Online (Sandbox Code Playgroud)

这允许您在非常复杂的情况下遍历字符串并在匹配过程中执行操作。我想我在掌握 Perl中有一些例子。