【问题标题】:Perl: Method to convert regexp with greedy quantifiers to non-greedyPerl:将带有贪婪量词的正则表达式转换为非贪婪的方法
【发布时间】:2021-05-01 09:58:59
【问题描述】:

我的用户给出了一个带有量词的正则表达式,这些量词默认为贪婪。他可以给出任何有效的正则表达式。所以解决方案必须处理用户可以扔给我的任何东西。

如何转换正则表达式,使任何贪婪的量词都是非贪婪的?

Perl 是否有一个 (?...:regexp) 构造,它将量词的贪婪默认值强制为非贪婪默认值?

如果不是:有没有其他方法可以将带有贪婪量词的正则表达式强制转换为非贪婪量词?

例如,用户可以输入:

.*
[.*]
[.*]{4,10}
[.*{4,10}]{4,10}

虽然这四个例子看起来很相似,但它们的含义却完全不同。

如果您只是在每个*/} 之后添加?,您将更改最后三个示例中的字符集。

相反,它们应该更改为/行为如下:

.*?
[.*]
[.*]{4,10}?
[.*{4,10}]{4,10}?

但是匹配的字符串是最小匹配,而不是第一个匹配,Perl 将默认为:

$a="aab";

$a=~/(a.*?b)$/;
# Matches aab, not ab
print $1;

但是考虑到非贪婪的正则表达式,最小匹配可能可以通过添加.*来获得:

$a="aab";

$a=~/.*(a.*?b)$/;
# Matches ab
print $1;

【问题讨论】:

  • 也许您应该询问特定的正则表达式或量词。
  • 看起来不难,是吗? perlre 中“贪婪”的第二个结果回答了您的问题
  • @TLP 问题是正则表达式是由 the user 给出的!我无法控制他们输入的内容,所以我需要一种使用 any 正则表达式的方法。例如。他们很可能会输入:'[(?:.*)]',它不包含任何量词,但看起来是这样 - 相反它是一个字符集。
  • @ikegami 它确实回答了这个问题。如果您不明白为什么,请重新阅读问题。您的参考文献列出了非贪婪量词。它没有显示将贪婪正则表达式转换为非贪婪正则表达式的一般方法。如问题所示,将? 添加到每个* 的天真方法将不起作用。
  • “将贪婪的正则表达式强制转换为非贪婪的构造” 这是否存在于 any 正则表达式风格中?我从来没有听说过。此外,正如 TLP 所说,这 99% 是 XY 问题。更改用户的模式以匹配与他们想要匹配的内容不同的内容,您究竟能获得什么?

标签: regex perl


【解决方案1】:

“贪婪”不是整个正则表达式的属性。它是量词的一个属性。

可以对每个量词分别进行控制。只需在量词后添加? 即可使其不贪婪,例如

[a-z]*?

a{2,3}?

[0-9]??

\s+?

不,没有任何内置方法可以将整个正则表达式转换为某种“默认非贪婪”模式。您需要解析正则表达式,检测所有量词并相应地更改它们。也许在 CPAN 的某个地方有一个正则表达式解析库。


到目前为止我找到的最接近的是Regexp::Parser module。我没有尝试过,但看起来它可以解析正则表达式,遍历树,进行适当的更改,然后构建修改后的正则表达式。请看一下。

【讨论】:

  • 一个相应地改变量化的函数将回答这个问题。
  • @Ole Tange,要求我们编写正则表达式解析器方式太宽泛了,并且要求库/工具推荐也是题外话。
【解决方案2】:

您可以使用状态机:

#!/usr/bin/perl

use strict;
use warnings;

my @regexes = ( ".*", "[.*]", "[.*]{4,10}", "[.*{4,10}]{4,10}" );

for (@regexes) {
    print "give: $_\n";
    my $ungreedy = make_ungreedy($_,0);
    print "got:  $ungreedy\n";
    print "============================================\n"
}


sub make_ungreedy {
    my $regex = shift;

    my $class_state  = 0;
    my $escape_state = 0;
    my $found        = 0;
    my $ungreedy     = "";

    for (split (//, $regex)) {
        if ($found) {
            $ungreedy .= "?" unless (/\?/);
            $found = 0;
        }
        $ungreedy .= $_;

        $escape_state = 0, next if ($escape_state);
        $escape_state = 1, next if (/\\/);
        $class_state  = 1, next if (/\[/);
        if ($class_state) {
            $class_state = 0 if (/\]/);
            next;
        }
        $found = 1 if (/[*}+]/);
    }
    $ungreedy .= '?' if $found;
    return $ungreedy;
}

【讨论】:

  • 您需要扩展它以正确处理非捕获组(?:group),手动重新编号组(?|...),关闭不属于量词的} - 例如hello},作为字符类的一部分打开 [ - 例如[a[b],还有所有格量词,例如X{2,3}+、Unicode 字符类\p{IsDigit}
  • 是的,当然。我会评论的。但目前实施它会持续很长时间。也许最好用 perl 调用另一个解析器。然后应该有一个/U 标志。
  • 我回滚了? 的处理,所以非捕获组等应该没有问题。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2012-01-23
  • 1970-01-01
  • 2011-08-29
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多