如何在没有插值的正则表达式中多次匹配行尾?

时间:2010-05-20 18:08:23

标签: regex perl interpolation

如果我输入了新行,例如:

[INFO]
xyz
[INFO]

如何使用$锚点拉出xyz部分?我试过像/^\[INFO\]$(.*?)$\[INFO\]/ms这样的模式,但perl给了我:

Use of uninitialized value $\ in regexp compilation at scripts\t.pl line 6.

有没有办法关闭插值,以便锚点按预期工作?

编辑:关键是行尾锚是一个美元符号,但有时可能需要通过模式散布行尾锚。如果模式是插值,那么您可能会遇到诸如未初始化$\之类的问题。例如,这里可接受的解决方案是/^\[INFO\]\s*^(.*?)\s*^\[INFO\]/ms,但这并不能解决第一个问题的症结所在。我已经将锚点更改为^,因此没有插值,并且通过此输入我可以自由地执行此操作。但是,当我真的想在我的模式中使用$引用EOL时呢?如何编译正则表达式?

5 个答案:

答案 0 :(得分:4)

问题是学术性的 - 无论如何都不需要你的正则表达式中的$锚。您应该使用\n来匹配换行符,因为$仅匹配换行符与其前面的字符之间的差距。

编辑:我想说的是,你从不需要以这种方式使用$。从一行到下一行的任何匹配都必须使用行分隔符以某种方式。考虑一下你的例子:

/^\[INFO\]$(.*?)$\[INFO\]/ms

如果这确实编译了,(.*?)将首先消费第一个换行符并继续前进,直到匹配\nxyz,其中第二个$将成功。但是下一个字符是换行符,正则表达式正在寻找[,所以这不起作用。回溯后,(.*?)会不情愿地再消耗一个字符 - 第二个换行符 - 但$会失败。

每次尝试将EOL与$匹配,然后再添加更多内容时,您必须匹配的第一个“内容”就是换行符,那么为什么不匹配呢?这就是为什么Perl正则表达式编译器试图将$\解释为正则表达式中的变量名称的原因:使用行尾锚点后跟一个不是行分隔符的字符是没有意义的。

答案 1 :(得分:4)

根据perlfaq6 - How can I pull out lines between two patterns that are themselves on different lines?中的答案,这是一个单行的样子:

perl -0777 -ne 'print $1,"\n" while /\[INFO\]\s*(.*?)\s*\[INFO\]/sg' file.txt

-0777开关立即在整个文件中徘徊。

但是,如果您正在使用一个子程序,可以灵活地选择要提取的标记,那么File::Slurp模块会让事情变得更容易:

use strict;
use warnings;
use File::Slurp qw/slurp/;

sub extract {

    my ( $tag, $fileName ) = @_;
    my $text = slurp $fileName;

    my ($info) = $text =~ /$tag\s*(.*?)\s*$tag/sg;
    return $info;
}

# Usage:
extract ( qr/\[INFO\]/, 'file.txt' );

答案 2 :(得分:4)

当正则表达式变得过于棘手时,它们可能是错误的工具。我可能会考虑在这里使用触发器操作符。如果它的左侧是真的那么它是假的,然后保持为真,直到它的右侧为真。这样,您可以通过查看单独的行来选择开始和结束提取的位置:

my $string = <<'HERE';
[INFO]
xyz
[INFO]
HERE

open my $string_fh, '<', \$string;

while( <$string_fh> )
    {
    next if /\[INFO]/ .. /\[INFO]/;
    chomp;

    print "Extracted <$_>\n";
    }

如果您使用的是Perl 5.10,则可以在正则表达式中使用结束\R的广义行:

use 5.010;

my $string = <<'HERE';
[INFO]
xyz
[INFO]
HERE

my( $extracted ) = $string =~ /(?:\A|\R)\[INFO]\R(.*?)\R\[INFO]\R/;

print "Extracted <$extracted>\n";

不要挂在最后的锚点上。

答案 3 :(得分:1)

也许/x修饰符可以提供帮助:

m/ ^\[INFO\] $ # Match INFO line
   \n
   ^ (.*?) $ # Collect desired line
   \n 
   ^ \[INFO\] # Match another INFO line
/xms

我没有测试过,所以你可能需要调试它。但我认为这会阻止$符号作为变量进行插值。

答案 4 :(得分:1)

虽然我已经接受了艾伦摩尔的回答(瑞恩汤普森的答案也会做得太糟糕,我只能接受一个)我想完全清楚解决方案,因为它有点埋没在评论和讨论中。下面的Perl脚本演示了Perl使用$来插入变量,如果任何字符继续进行美元符号,并且关闭插值将允许$被视为EOL。

use strict;
use warnings;

my $x = "[INFO]\nxyz\n[INFO]";
if( $x =~ /^\[INFO\]$\n(.*?)$\n\[INFO\]/m ) {
    print "'$1' FOUND\n";
} else {
    print "NO MATCH FOUND\n";
}

if( $x =~ m'^\[INFO\]$\n(.*?)$\n\[INFO\]'m ) {
    print "'$1' FOUND\n";
} else {
    print "NO MATCH FOUND\n";
}

if( $x =~ m/ ^\[INFO\] $ # Match INFO line
\n
^ (.*?) $ # Collect desired line
\n 
^ \[INFO\] # Match another INFO line
/xms ) {
    print "'$1' FOUND\n";
} else {
    print "NO MATCH FOUND\n";
}

该脚本生成以下输出:

Use of uninitialized value $\ in regexp compilation at t.pl line 5.
Use of uninitialized value $\ in regexp compilation at t.pl line 5.
NO MATCH FOUND
'xyz' FOUND
'xyz' FOUND