perl 为什么=~只求值一次?

2o7dmzc5  于 11个月前  发布在  Perl
关注(0)|答案(3)|浏览(95)

在此示例脚本中:

#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
no


预期输出为:

yes
yes


我可以通过打印字符串来确认它没有通过运行正则表达式匹配而发生变化。
为什么Perl似乎只对正则表达式求值一次?我在Google或手册上找不到关于这方面的信息,而且这种行为对我来说并不直观。我希望每次你对正则表达式匹配求值时,它都是从fresh开始的,并且不记得任何关于前一个匹配的信息。

编辑:对于未来的上下文,这个问题是在我发现一段代码后提出的,看起来像这样:

while ( $foo =~ /pattern/g) { $some_incrementing_var++ };


我最初并不理解这个while循环是如何终止的,因为乍一看,它看起来像一个无限循环。

vu8f3i0k

vu8f3i0k1#

标量上下文中的//g开始匹配上一个/g匹配结束的位置。由于cat只出现一次,因此它返回false以指示第二次没有匹配。
这在循环中很有用:

while ( /\w+/g ) {
   say $&;
}

字符串
我们可以用pos来看看发生了什么。

local $_ = "abc def ghi";
while ( ( say pos // 0 ), /\w+/g ) {
   say $&;
}
0
abc
3
def
7
ghi
11

的数据
但是,虽然这在循环中很有用,但它对if ( //g )没有意义(除非你正在展开一个循环)。这意味着什么?“检查它是否匹配,并毫无理由地继续检查更多的匹配”?显然,这没有意义。删除g以打印yes两次。

i7uq4tfw

i7uq4tfw2#

如果省略//g(“global”)修饰符,将得到预期的输出:

$foo = "batcathat";

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

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

字符串
我认为没有必要在代码中使用//g

jutyujz0

jutyujz03#

标量上下文中的/g是我最喜欢的工具之一,它在perlop中的文档非常长。对于它的文档来说,这似乎是一个奇怪的地方,但是有一些标志会影响匹配操作符的工作方式,还有一些标志会影响模式的工作方式(参见Know the difference between regex and match operator flags)。
@ikegami提到了pos,你可以在perlfunc中读到它。Perl跟踪它在a字符串中的位置。在标量上下文中没有/g,匹配操作符从字符串的开头开始,向结尾移动。匹配完成后,它就完成了。下一个匹配再次从字符串的开头开始。
\g稍微改变了这一点。第一个匹配从字符串的开头开始,匹配(如果可以的话),并设置匹配结束的位置后一个位置。这就是pos。下一次使用\g,匹配从pos开始。在您的情况下,
在列表上下文中,它也在做同样的事情,但会用尽。它进行第一个匹配,然后从该位置开始尝试下一个匹配。这就是为什么列表上下文中的匹配无法找到重叠匹配:它在重叠开始的地方开始匹配。Jeffrey Friedl的Mastering Regular Expressions,尽管已经很老了,是一个很好的关于正则表达式如何工作的调查,它们可以以不同的方式工作,以及各种方式如何排除其他方式可能具有的特征。

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";
}

字符串
这将产生:

pos is 0
yes
pos is 6
no


这个位置是按字符串跟踪的,并且在\g匹配失败后重置(就像其他regex副作用变量一样):

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";
}


输出显示两次查找cat的尝试都成功了,因为dog尝试失败并重置了pos

pos is 0
yes
pos is 0
yes


但是,也有一种方法可以解决这个问题。/c标志告诉match操作符在失败时不要重置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";
}


现在,您可以返回到原始输出:

pos is 0
yes
pos is 6
no


这允许你做类似这个非常简单的例子的事情。你可以匹配某个特定的东西,如果这不起作用,尝试其他东西,而不会丢失你在字符串中的位置:

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();
}


这允许你在非常复杂的情况下遍历字符串,并在匹配过程中做一些事情,我想我在Mastering Perl中有一些例子。

相关问题