是否可以删除已经匹配的捕获组,使其不参与?

时间:2019-01-04 23:33:28

标签: regex pcre regex-group conditional-regex

在PCRE2或任何其他支持正向反向引用的正则表达式引擎中,是否有可能将在循环的先前迭代中匹配的捕获组更改为非参与捕获组(也称为未设置捕获组未捕获组),导致测试该组的条件与“ false”子句而非“ true”子句匹配吗?

例如,使用以下PCRE正则表达式:

^(?:(z)?(?(1)aa|a)){2}

当输入字符串zaazaa时,它会根据需要匹配整个字符串。但是当喂饱zaaaa时,我希望它与zaaa相匹配;而是匹配整个字符串zaaaa。 (这仅是为了说明。当然,该示例可以由^(?:zaa|a){2}处理,但这并不重要。捕获组擦除的实际用法往往会处于循环中,大多数情况下要进行两次以上的迭代。)

执行此操作的另一种方法,该方法也无法按需工作:

^(?:(?:z()|())(?:\1aa|\2a)){2}

请注意,当循环“展开”时,这两种方法都可以按需工作,因为它们不再需要擦除已经进行的捕获:

^(?:(z)?(?(1)aa|a))(?:(z)?(?(2)aa|a))
^(?:(?:z()|())(?:\1aa|\2a))(?:(?:z()|())(?:\3aa|\4a))

因此,不能使用最简单的条件形式,而必须使用更复杂的条件形式,该条件仅在此示例中有效,因为z的“ true”匹配为非空:

^(?:(z?)(?(?!.*$\1)aa|a)){2}

或者仅使用模拟条件:

^(?:(z?)(?:(?!.*$\1)aa|(?=.*$\1)a)){2}

我搜寻了所有可以找到的文档,并且似乎甚至没有提及或明确描述此行为(即使在循环中反复捕获,即使无法重新捕获,该捕获仍会在该循环中持续存在) )。

这与我的直觉期望有所不同。我实现它的方式是,对重复次数为0的捕获组进行评估会擦除/重置(因此,任何使用*?{0,N}量词的捕获组都会发生这种情况),但由于它在前一次迭代中获得了捕获的同一组内处于并行替代状态而跳过了它,因此不会删除它。因此,如果它们contain at least one of every vowel,则此正则表达式仍会匹配单词:

\b(?:a()|e()|i()|o()|u()|\w)++\1\2\3\4\5\b

但是跳过捕获组,因为它位于未评估重复的非零评估组中,嵌套在嵌套组中,捕获组在上一次迭代中会取一个值,这将是非零重复 擦除/取消设置它,因此此正则表达式将能够在循环的每次迭代中捕获或擦除组\1

^(?:(?=a|(b)).(?(1)_))*$

,并将匹配诸如aaab_ab_b_aaaab_ab_aab_b_b_aaa之类的字符串。但是,前向引用的方式实际上是在现有引擎中实现的,它与aaaaab_a_b_a_a_b_b_a_b_b_b_相匹配。

我想知道这个问题的答案,不仅是因为它在构建正则表达式中很有用,而且还因为我拥有written my own regex engine(目前与某些可选扩展(包括分子先行{{1})兼容的ECMAScript。 },即非原子先行(据我所知,没有其他引擎具有此功能),并且我想继续添加其他引擎的功能,包括前向/嵌套反向引用。我不仅希望我的前向后向引用实现与现有实现兼容,而且,如果没有 擦除其他引擎中的捕获组的方法,我可能会创建一种方法来实现与其他现有正则表达式功能不冲突的引擎。

要明确:只要有足够的研究和/或引用来源作为后盾,就可以回答任何主流引擎都不可能做到这一点。陈述它 可能的答案将更容易陈述,因为它只需要一个示例。

有关不参与捕获组的一些信息:
http://blog.stevenlevithan.com/archives/npcg-javascript-这是最初向我介绍这个想法的文章。
https://www.regular-expressions.info/backref2.html-此页面的第一部分进行了简要说明。
在ECMAScript / Javascript正则表达式中,对NPCG的反向引用始终匹配(进行零长度匹配)。在几乎所有其他正则表达式中,它们都无法匹配任何东西。

4 个答案:

答案 0 :(得分:5)

我发现此内容记录在PCRE的手册页的“ PCRE2和PERL之间的差异”下:

   12.  There are some differences that are concerned with the settings of
   captured strings when part of  a  pattern  is  repeated.  For  example,
   matching  "aba"  against  the  pattern  /^(a(b)?)+$/  in Perl leaves $2
   unset, but in PCRE2 it is set to "b".

我正在努力思考一个实际问题,用替代解决方案无法更好地解决该问题,但是为了保持简单,这里有:

假设您有一个简单的任务很适合通过使用前向引用来解决;例如,检查输入字符串是否是回文。通常无法通过递归来解决此问题(由于子例程调用的原子性质),因此我们展开了以下内容:

/^(?:(.)(?=.*(\1(?(2)\2))))*+.?\2$/

足够容易。现在假设我们被要求验证输入中的每行都是回文。让我们尝试通过将表达式放在重复的组中来解决此问题:

\A(?:^(?:(.)(?=.*(\1(?(2)\2))))*+.?\2$(?:\n|\z))+\z

显然这是行不通的,因为\ 2的值从第一行一直保留到下一行。这与您面临的问题类似,因此有多种解决方法:

1。将整个子表达式包含在(?!(?! ))中:

\A(?:(?!(?!^(?:(.)(?=.*(\1(?(2)\2))))*+.?\2$)).+(?:\n|\z))+\z

非常容易,只要将它们推入其中,您基本上就可以进入。如果要保留任何特定的捕获值,则不是一个很好的解决方案。

2。分支重置组以重置捕获组的值:

\A(?|^(?:(.)(?=.*(\1(?(2)\2))))*+.?\2$|\n()()|\z)+\z

使用此技术,您可以将捕获组的值从第一个(在这种情况下为\ 1)重置为某个值(在此处为\ 2)。如果您需要保留\ 1的值但擦除\ 2,则此技术将无效。

3。引入一个从特定位置捕获字符串其余部分的组,以帮助您以后识别自己的位置:

\A(?:^(?:(.)(?=.*(\1(?(2)(?=\2\3\z)\2))([\s\S]*)))*+.?\2$(?:\n|\z))+\z 

所有其余的行集合都保存在\ 3中,从而使您能够可靠地检查是否已前进到下一行(当(?=\2\3\z)不再为真时)。

这是我最喜欢的技术之一,因为它可以用于解决似乎不可能完成的任务,例如ol'matching nested brackets using forward references。使用它,您可以维护所需的任何其他捕获信息。唯一的缺点是它效率极低,特别是对于较长的对象。

4。这并不能真正回答问题,但可以解决问题:

\A(?![\s\S]*^(?!(?:(.)(?=.*(\1(?(2)\2))))*+.?\2$))

这是我正在谈论的替代解决方案。基本上,“重写模式” :)有时候是可能的,有时不是。

答案 1 :(得分:5)

使用PCRE(据我所知),无法取消捕获组的设置,而是使用子例程调用,因为它们的性质无法记住上一次递归的值,因此您可以完成相同的任务:

(?(DEFINE)((z)?(?(2)aa|a)))^(?1){2}

请参见live demo here

如果要在自己的正则表达式中实现某种行为以取消捕获组的设置,我强烈建议不要让它自动发生。只需提供一些标志即可。

答案 2 :(得分:4)

在.NET的正则表达式中,这部分可行。

要注意的第一件事是,.NET记录了所有捕获的对于给定的捕获组,而不仅仅是最新的。例如,^(?=(.)*)记录在第一行作为组在一个单独的捕获的每个字符。

要实际删除捕获,.NET正则表达式具有称为balancing groups的构造。这种结构的完整格式为(?<name1-name2>subexpression)

  • 首先,name2必须先前已被捕获。
  • 然后子表达式必须匹配。
  • 如果存在name1,则捕获name2到结束子表达式匹配之间的子字符串将捕获到name1中。
  • 然后删除name2的最新捕获。 (这意味着可以在子表达式中向后引用旧值。)
  • 匹配项前进到子表达式的末尾。

如果你知道你已经name2捕获恰好一次则可以容易地使用被删除(?<-name2>);如果你不知道你是否有name2捕获,那么你可以使用(?>(?<-name2>)?)或条件。问题就出现了,如果你可能有name2捕获不止一次因为那就要看你是否能组织的缺失足够的重复name2。 ((?<-name2>)*不起作用,因为*是相当于?对于零长度匹配。)

答案 3 :(得分:0)

还有另一种方法可以在 .NET 中“擦除”捕获组。与 (?<-name>) 方法不同,这会清空组而不是删除它——因此它不是不匹配,而是匹配一个空字符串。

在 .NET 中,可以多次捕获具有相同名称的组,即使该名称是一个数字。这允许将使用平衡组的 PCRE 表达式移植到 .NET。考虑这个 PCRE 模式:

(?|(pattern)|())

假设两个组都是 \1 以上,那么使用这种技术,在 .NET 中它会变成:

(?:(pattern)|(?<1>))

我今天使用这种技术制作了一个 38 字节的 .NET 正则表达式,matches strings whose length is a fourth power

^((?=(?>^((?<3>\3|x))|\3(\3\2))*$)){2}

以上是以下35字节PCRE正则表达式的一个端口,使用了平衡组:

^((?=(?|^((\2|x))|\2(\2\3))*+$)){2}

(在这个例子中,捕获组实际上并没有被清空。但是这个技术可以用来做一个平衡组可以做的任何事情,包括清空一个组。)