3

我正在用 perl 编写一个正则表达式来匹配启动 perl 子例程定义的 perl 代码。这是我的正则表达式:

my $regex = '\s*sub\s+([a-zA-Z_]\w*)(\s*#.*\n)*\s*\{';

$regex 匹配启动子例程的代码。我还试图捕获 $1 中子例程的名称以及子例程名称和 $2 中的初始左大括号之间的任何空格和注释。2美元给我带来了麻烦。

考虑以下 perl 代码:

my $x = 1;

sub zz
# This is comment 1.
# This is comment 2.
# This is comment 3.
{
    $x = 2;
    return;
}

当我将此 perl 代码放入字符串并将其与 $regex 匹配时,$2 是“# This is comment 3.\n”,而不是我想要的三行注释。我以为正则表达式会贪婪地将所有三行注释放入 $2 中,但似乎并非如此。

我想了解为什么 $regex 不起作用并设计一个简单的替代品。正如下面的程序所示,我有一个更复杂的替换($re3)可以工作。但我认为理解 $regex 为什么不起作用对我来说很重要。

use strict;
use English;

my $code_string = <<END_CODE;
my \$x = 1;

sub zz
# This is comment 1.
# This is comment 2.
# This is comment 3.
{
    \$x = 2;
    return;
}
END_CODE

my $re1 = '\s*sub\s+([a-zA-Z_]\w*)(\s*#.*\n)*\s*\{';
my $re2 = '\s*sub\s+([a-zA-Z_]\w*)(\s*#.*\n){0,}\s*\{';
my $re3 = '\s*sub\s+([a-zA-Z_]\w*)((\s*#.*\n)+)?\s*\{';

print "\$code_string is '$code_string'\n";
if  ($code_string =~ /$re1/) {print "For '$re1', \$2 is '$2'\n";}
if  ($code_string =~ /$re2/) {print "For '$re2', \$2 is '$2'\n";}
if  ($code_string =~ /$re3/) {print "For '$re3', \$2 is '$2'\n";}
exit 0;

__END__

上面 perl 脚本的输出如下:

$code_string is 'my $x = 1;

sub zz
# This is comment 1.
# This is comment 2.
# This is comment 3.
{
    $x = 2;
    return;
} # sub zz
'
For '\s*sub\s+([a-zA-Z_]\w*)(\s*#.*\n)*\s*\{', $2 is '# This is comment 3.
'
For '\s*sub\s+([a-zA-Z_]\w*)(\s*#.*\n){0,}\s*\{', $2 is '# This is comment 3.
'
For '\s*sub\s+([a-zA-Z_]\w*)((\s*#.*\n)+)?\s*\{', $2 is '
# This is comment 1.
# This is comment 2.
# This is comment 3.
'
4

3 回答 3

7

查看您的正则表达式中捕获$2. 它是(\s*#.*\n)。就其本身而言,这只能捕获单个注释行。您在它后面有一个星号以捕获多个注释行,这很好用。它捕获多个注释行并将它们中的每$2一个逐个放入 中,每次替换之前的值$2$2因此,正则表达式完成匹配的最终值是捕获组匹配的最后一件事,即最后的注释行。仅有的。要修复它,您需要将星号放在捕获组中。但是随后您需要添加另一组括号(这次是非捕获),以确保星号适用于整个事物。所以而不是(\s*#.*\n)*,你需要((?:\s*#.*\n)*)

您的第三个正则表达式有效,因为您无意中将整个表达式括在括号中,以便您可以在它后面加上一个问号。这导致$2一次捕获所有评论,并且$3只捕获最终评论。

当您调试您的正则表达式时,请确保您打印出您正在使用的所有匹配变量的值:$1$2$3等。您会看到这$1只是子例程的名称,并且$2只是第三条注释。这可能会让您想知道,当第一个和第二个捕获组之间没有任何内容时,您的正则表达式究竟是如何跳过前两个注释的,这最终会引导您发现当一个捕获组多次匹配时会发生什么。

顺便说一句,您似乎还在将子例程名称后的任何空格捕获到$1. 这是故意的吗?(糟糕,我搞砸了我的助记符,以为\w是“w 代表空白”。)

于 2012-03-13T20:02:50.193 回答
4

如果将重复添加到捕获组,它将仅捕获该组的最终匹配项。这就是为什么$regex只匹配最后的注释行。

这是我将如何重写你的正则表达式:

my $regex = '\s*sub\s+([a-zA-Z_]\w*)((?:\s*#.*\n)*)\s*\{';

这与您的 非常相似$re3,除了以下更改:

  • 空白和评论匹配部分现在位于非捕获组中
  • ((...)+)?我将正则表达式的那部分更改((...)*)为等效的部分。
于 2012-03-13T19:59:09.950 回答
1

问题是默认情况下\n不是字符串的一部分。正则表达式在 停止匹配\n

您需要使用s修饰符进行多行匹配:

if  ($code_string =~ /$re1/s) {print "For '$re1', \$2 is '$2'\n";}

注意s正则表达式之后。

于 2012-03-13T19:55:51.723 回答