我正在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.
'
答案 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
。