递归正则表达式匹配括号内所有内容 (PCRE)

4

我惊讶地发现在SO上没有易于查找并带有答案的类似问题。我想要匹配一些函数中的所有内容。想法是删除无用的函数。

foo(some (content)) --> some (content)

我正在尝试匹配函数调用中的所有内容,其中可能包括括号。这是我的PCRE正则表达式:

(?<name>\w+)\s*\(\K
(?<e>
     [^()]+
     |
     [^()]*
         \((?&e)\)
     [^()]*
)*
(?=\))

https://regex101.com/r/gfMAIM/1

很遗憾它不起作用,而且我真的不太明白为什么。


被检查的函数调用是否总是在行首? - Superluminal
@Predicate 假设所有内容都在同一行上,但您可以在此行上找到多个函数调用:foo(); bar()\n - nowox
有没有一种方法可以使所需匹配中的括号不平衡? - Superluminal
3个回答

4

你的 Group e 模式不能正确工作,目前它只匹配深度为 1 的括号,因为你只递归了一次 e 模式。它需要匹配所有存在的 (...) 子字符串,因此,子程序模式需要在一个 *+ 量化组内,并且甚至可以“简化”为 (?<e>[^()]*(?:\((?&e)\)[^()]*)*)

请注意,你的 Group e 模式等同于 (?<e>[^()]+|\((?&e)\))*。在 \((?&e)\) 周围使用 [^()]* 是多余的,因为 [^()]+ 替代项将消耗当前深度级别上除 () 之外的字符。

此外,你量化了 Group e 模式,使其成为一个重复捕获组,仅保留最后一次迭代期间匹配的文本。

你可以使用

(?<name>\w+)\s*\(\K(?<e>[^()]*(?:\((?&e)\)[^()]*)*)(?=\))

请查看正则表达式演示

详细信息

  • (?<name>\w+)\s*\(\K - 匹配一个或多个单词字符,0个或多个空格和被省略的(,但不包括这个(
  • (?<e> - 开始匹配组e
    • [^()]* - 匹配0个或多个除了()之外的任何字符
    • (?: - 开始一个非捕获组:
      • \( - 一个(字符
      • (?&e) - 组e模式递归
      • \) - 一个)字符
      • [^()]* - 匹配0个或多个除了()之外的任何字符
    • )* - 重复0次或更多次
  • ) - 结束组e
  • (?=\)) - 一个)字符必须紧挨着当前位置的右边。

似乎 OP 提出的正则表达式无法工作的原因是在 (?<e> 分组后紧接着的 *,需要在固定的 regex01 中添加一个未捕获的分组。 - Nahuel Fouilleul
1
另一种更有效避免回溯的解决方案是使用占有量词,例如 (?<name>\w+)\s*\(\K(?<e>(?:[^()]++|\((?&e)\))*)\) - Nahuel Fouilleul
@NahuelFouilleul 是的,这样会好一些。也可以使用原子组 (?<name>\w+)\s*\(\K(?<e>(?>[^()]+|\((?&e)\))*)(?=\))。另外,请注意最后一个 ) 必须在前瞻中(至少是 OP 的逻辑)。 - Wiktor Stribiżew
伙计们,你们的solution在括号不平衡的情况下会失效。它可能出现在字符串中。 - Superluminal
@Predicate 这些情况超出了递归匹配的范围,如果期望匹配的两端没有特定的上下文信息,那么只能在已知特定上下文信息的情况下进行处理。对于这些情况,你不能有一个“通用”的正则表达式。 - Wiktor Stribiżew
@Predicate 也可能出现在字符串中,而且你的解决方案也会失败,但是可以使用回溯动词来丢弃字符串,类似于 "(?:[^"]++|\\.*)*"(*SKIP)(?!)|... - Nahuel Fouilleul

1
以下正则表达式可以在不采取额外步骤的情况下进行匹配:
(?<name>\w+)\s*(\((?<e>([^()]*+|(?2))+)\))

请点击此处查看演示。

但是,以下字符串包含引号内的不平衡括号,与上述规则不符:

  • foo(bar = ')')
  • foo(bar(john = "(Doe..."))

因此,您应该寻找:

(?<name>\w+)\s*(\((?<e>([^()'"]*+|"(?>[^"\\]*+|\\.)*"|'(?>[^'\\]*+|\\.)*'|(?2))+)\))

在这里查看实时演示

正则表达式分解:

  • (?<name>\w+)\s* 匹配函数名称及其后面的空格
  • ( 开始一个集群
    • \( 匹配文字 (
    • (?<e> 开始命名捕获组 e
      • ( 开始捕获组 #2
        • [^()'"]*+ 匹配除了()'"之外的任何字符
        • | 或者
        • "(?>[^"\\]*+|\\.)*" 匹配双引号之间的任何内容
        • | 或者
        • '(?>[^'\\]*+|\\.)*' 匹配单引号之间的任何内容
        • | 或者
        • (?2) 递归第二个捕获组
      • )+ 尽可能重复,至少一次
    • ) 结束捕获组
    • \) 匹配文字 )
  • ) 结束捕获组

0

我有一个简单的正则表达式,没有递归

(?<=[\w ]{2}\().*(?=\))

目前它处理不平衡的括号,但是它不能处理在同一行上有多个函数的情况。如果您知道函数之间的分隔符,例如Java代码中的;,则可以处理它们。

变体2(已更新以处理同一行上的多个函数):

(?<=[\w ]\()[^;\n]*(?=\))

变体3(允许在字符串中使用;):

(?<=[\w ]\()([^;\n]|".*?")*(?=\))    

变体 4(转义字符串):

(?<=[\w \n]\()(?:[^;\n"]|(?:"(?:[^"]|\\")*?(?<!\\)"))*(?=\))

你可以改进第一个lookbehind,使其不仅检查一个字符,但是由于PCRE不允许具有可变长度的Lookbehinds,所以这项工作变得更加困难。可能需要更多的上下文来解决这个问题。但是我认为在(之前只检查一个字符没有问题。 - Superluminal
变量3现在将因为f("a;");g("b");而失败,修复方法可能是(?<=[\w ]\()([^";\n]|"(?:[^"]++|\\.*)*"(*SKIP))*(?=\)) - Nahuel Fouilleul
好的,f("a"){""};g("\"b;"); 或者只是 f("a")+f("b"); - Nahuel Fouilleul
@revo 正如我们在一些较高层次的正则表达式中看到的那样,递归也不能保证它。不过没关系,我喜欢你们的解决方案,但我真的很喜欢我的正则表达式的简单性。 - Superluminal
第四个正则表达式在许多正确的匹配中失败,例如 bar(a =") ", b = " );")。这是一个无休止的修补过程。你必须使用递归来解决这个问题。顺便说一下,递归可以保证成功。 - revo
显示剩余8条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接