Perl正则表达式不够贪心

时间:2012-03-13 19:51:41

标签: regex perl regex-greedy

我正在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是“#这是注释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.
'

3 个答案:

答案 0 :(得分:7)

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

你的第三个正则表达式是有效的,因为你无意中将括号中的整个表达式包围起来,以便你可以在它后面添加一个问号。这导致$2一次捕获所有评论,$3仅捕获最终评论。

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

顺便说一句,看起来你也在将子程序名称后面的任何空格捕获到$1。这是故意的吗?(哎呀,我弄乱了我的助记符,并认为\w是“w for whitespace”。)

答案 1 :(得分:4)

如果您向捕获组添加重复,它将仅捕获该组的最终匹配。这就是$regex仅匹配最终评论行的原因。

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

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

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

  • 空格和评论匹配部分现在处于非捕获组
  • 我将正则表达式中的那部分从((...)+)?更改为((...)*),这是等效的。

答案 2 :(得分:1)

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

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

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

注意正则表达式后的s