代码之家  ›  专栏  ›  技术社区  ›  harschware

如何在regex中多次匹配行尾而不进行插值?

  •  3
  • harschware  · 技术社区  · 14 年前

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

    [INFO]
    xyz
    [INFO]
    

    如何使用 $ 锚?我试过一种模式 /^\[INFO\]$(.*?)$\[INFO\]/ms 但是Perl给了我:

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

    是否有方法关闭插值,以便锚按预期工作?

    编辑:关键是行尾锚是一个美元符号,但有时可能需要通过模式散布行尾锚。如果模式是内插的,那么可能会出现未初始化等问题。 $\ . 例如,这里可接受的解决方案是 /^\[INFO\]\s*^(.*?)\s*^\[INFO\]/ms 但这并不能解决第一个问题的症结。我把锚改成了 ^ 所以没有插值,有了这个输入,我可以自由地做。但当我真的想参考EOL时呢 $ 按照我的模式?如何让regex编译?

    5 回复  |  直到 14 年前
        1
  •  4
  •   Alan Moore Chris Ballance    14 年前

    这个问题是学术性的——没有必要 $ 锚在你的regex无论如何。你应该用 \n 以匹配换行符,因为 $ 只匹配换行符和它前面的字符之间的间隙。

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

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

    如果这确实编译了, (.*?) 一开始会消耗第一个换行符,一直到它匹配为止。 \nxyz ,第二个 $ 会成功的。但下一个字符是换行符,regex正在查找 [ 所以这不起作用。回溯之后, (?*) 会不情愿地再消耗一个字符——第二个换行符——但是 $ 会失败。

    任何时候你试图匹配一个EOL与 $ 然后更多的东西,你要匹配的第一个“东西”是linefeed,那么为什么不匹配呢?这就是PerlRegex编译器试图解释的原因 $\ 作为regex中的变量名:行末定位点后面跟一个不是行分隔符的字符是没有意义的。

        2
  •  4
  •   Zaid    14 年前

    根据中的答案 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 同时在整个文件中切换slurps。

    但是,如果您正在处理一个子例程,它使您能够灵活地选择要提取的标记,那么 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' );
    
        3
  •  4
  •   brian d foy    14 年前

    当正则表达式变得过于复杂时,它们可能是错误的工具。我可以考虑在这里使用触发器操作符。它是假的,直到它的左手边是真的,然后一直是真的,直到它的右手边是真的。这样,只需查看单个行,就可以选择从何处开始和结束提取:

    my $string = <<'HERE';
    [INFO]
    xyz
    [INFO]
    HERE
    
    open my $string_fh, '<', \$string;
    
    while( <$string_fh> )
        {
        next if /\[INFO]/ .. /\[INFO]/;
        chomp;
    
        print "Extracted <$_>\n";
        }
    

    如果您使用的是Perl5.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";
    

    不要挂在锚链末端。

        4
  •  1
  •   Ryan C. Thompson    14 年前

    也许 /x 修改器可以帮助:

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

    我还没有测试过,所以您可能需要调试它。但我认为这将阻止 $ 作为变量插入的符号。

        5
  •  1
  •   harschware    14 年前

    虽然我已经接受了阿兰·摩尔的回答(瑞安·汤普森的回答也会把这个技巧搞得太糟,我只能接受一个),但我还是想把解决方案说得非常清楚,因为它被埋没在评论和讨论中。下面的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