【问题标题】:Match from last occurrence using regex in perl在 perl 中使用正则表达式从最后一次出现匹配
【发布时间】:2018-09-05 20:04:57
【问题描述】:

我有这样的文字:

hello world /* select a from table_b
*/ some other text with new line cha
racter and there are some blocks of 
/* any string */ select this part on
ly 
////RESULT rest string

文本是多行的,我需要从最后一次出现的“*/”中提取直到“////RESULT”。在这种情况下,结果应该是:

 select this part on
ly 

如何在 perl 中实现这一点?

我尝试过\\\*/(.|\n)*////RESULT,但这将从第一个“*/”开始

【问题讨论】:

  • 你尝试了什么?
  • 我尝试过 \*/(.|\n)*////RESULT 但这将从第一个“*/”开始

标签: regex perl


【解决方案1】:

在这种情况下,一个有用的技巧是在正则表达式前面加上贪婪模式.*,它会在其余模式匹配之前尝试匹配尽可能多的字符。所以:

my ($match) = ($string =~ m!^.*\*/(.*?)////RESULT!s);

让我们把这个模式分解成它的组件:

  • ^.* 从字符串的开头开始并匹配尽可能多的字符。 (s 修饰符允许 . 匹配换行符。)字符串开头的锚点 ^ 不是绝对必要的,但它确保正则表达式引擎在匹配失败时不会浪费太多时间回溯.

  • \*/ 只匹配文字字符串*/。

  • (.*?) 匹配并捕获任意数量的字符; ? 使它变得不贪婪,因此它更倾向于匹配尽可能少的字符,以防正则表达式的其余部分可以匹配多个位置。

  • 最后,////RESULT 匹配自身。

由于该模式包含很多斜杠,并且我想避免使用leaning toothpick syndrome,因此我决定使用替代的正则表达式分隔符。感叹号 (!) 是一种流行的选择,因为它们不会与任何正常的正则表达式语法发生冲突。


编辑: 根据下面与 ikegami 的讨论,我想我应该注意,如果您想将此正则表达式用作较长正则表达式中的子模式,并且如果您想保证与(.*?) 匹配的字符串将永远包含////RESULT,那么您应该将正则表达式的这些部分包装在independent (?>) subexpression 中,如下所示:

my $regexp = qr!\*/(?>(.*?)////RESULT)!s;
...
my $match = ($string =~ /^.*$regexp$some_other_regexp/s);

(?>) 导致其中的模式失败,而不是接受次优匹配(即超出匹配////RESULT 的第一个子字符串的匹配),即使这意味着正则表达式的其余部分将无法匹配。

【讨论】:

  • 我讨厌.*?。它不能保证不匹配您希望它不匹配的内容。在这个特定的模式中,它工作得很好,除了它与纬度 */ 不匹配,如果它出现在 ////RESULT 之后。不过,这可能是件好事。
  • @ikegami:实际上,.*? 的行为非常明确;见"Combining RE pieces" in perlre。
  • 我从来没有说过它没有定义。我很熟悉它的作用和用途,而这两者不是一回事。给我一个带有两个.*? 的模式,我会告诉你一个错误或一个无用的?(抛开性能问题)。
  • 尽管我认为你的答案无法维护,但我还是给了 +1 的解释。
  • 啊,所以你发现.*? 不直观?如果“你希望”它会做的是“匹配除了紧随其后的模式之外的任何东西”,那么不,它不会那样做(尽管你可以通过包装它来获得这种效果(?>) 中的模式。我并不是真的不同意你的观点,我只是认为正确的答案不是“不要使用.*?”而是“除非你理解它的作用,否则不要使用.*?”。
【解决方案2】:
(?:(?!STRING).)*

匹配任意数量的不包含STRING 的字符。类似于[^a],但用于字符串而不是字符。

如果您知道不会遇到某些输入(如 Kenosis 和 Ilmari Karonen 所做的),您可以采取捷径,但这与您指定的内容相匹配:

my ($segment) = $string =~ m{
    \*/
    ( (?: (?! \*/ ). )* )
    ////RESULT
    (?: (?! \*/ ). )*
    \z
}xs;

如果你不关心*/是否出现在////RESULT之后,那么下面是最安全的:

my ($segment) = $string =~ m{
    \*/
    ( (?: (?! \*/ ). )* )
    ////RESULT
}xs;

如果最后一个*/ 后面有两个////RESULT,您没有指定会发生什么。以上匹配直到最后一个。如果你想匹配到第一个,你会使用

my ($segment) = $string =~ m{
    \*/
    ( (?: (?! \*/ | ////RESULT ). )* )
    ////RESULT
}xs;

【讨论】:

    【解决方案3】:

    这是一种选择:

    use strict;
    use warnings;
    
    my $string = <<'END';
    hello world /* select a from table_b
    */ some other text with new line cha
    racter and there are some blocks of 
    /* any string */ select this part on
    ly 
    ////RESULT
    END
    
    my ($segment) = $string =~ m!\*/([^/]+)////RESULT$!s;
    
    print $segment;
    

    输出:

     select this part on
    ly 
    

    【讨论】:

    • 谢谢,你能解释一下是什么!在 m 之后,[^/] 是什么?
    • 这不允许在*/ 和////RESULT 之间有任何/。 (也不能保证与最后一个 */ 匹配,但这可能不是问题。)
    • @PeitiPeterLi - 由于字符串中有正斜杠,m!! 用于匹配。请注意,ikegami 使用了 m{}。 [] 表示字符集,[^/] 表示不是 (^) 正斜杠。
    • @ikegami - 是的 - 关于正则表达式的优秀点,不包括捕获中的任何 /。我把这个问题简单化了。永远感谢你的 cmets。谢谢。
    猜你喜欢
    • 2021-07-18
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多