正则表达式如何多次匹配捕获组

4

我有以下正则表达式:

\{(\w+)(?:\{(\w+))+\}+\}

我需要它与以下任何一个相匹配。
{a{b}}

{a{b{c}}}

{a{b{c{d...}}}}

但是,如果在最后一个例子中使用正则表达式,它只会匹配两个组:ac,它不会匹配b和'c'或其他可能在它们之间的单词。

我该如何使每个单独的组都能匹配,比如:

group #1: a
group #2: b
group #3: c
group #4: d
group #4: etc...

或者像这样。
group #1: a
group #2: [b, c, d, etc...]

同时,我该如何确保左边有相同数量的 { 和右边的 },否则就不匹配呢?
感谢您的帮助,
David

1
@WiktorStribiżew 非常感谢,你能发表一个答案让我采纳吗? - David Callanan
对于支持递归的方言,正则表达式为{\w+(?:|(?R))} - Dmitry Egorov
@David,所以你在编写.NET代码?请注意,这个解决方案将会分离那些由嵌套的{}拆分的文本:{a{b{c{d}eee}}}将会分别捕获出ceee。这样可以吗? - Wiktor Stribiżew
@DmitryEgorov; 上面的PCRE解决方案并未检查整个字符串是否与模式匹配,并且它也没有保留“重复捕获”。如果进行一些修改,这个解决方案就可以与PyPi Python regex一起使用。 - Wiktor Stribiżew
@David,这是C#吗?如果您预期字符串的格式为{a{b{c{d}eee}}},请告诉我期望的行为是什么。 - Wiktor Stribiżew
显示剩余2条评论
2个回答

3

对于支持递归的正则表达式引擎(如PCRE、Ruby),您可以使用以下通用模式:

^({\w+(?1)?})$

它允许检查输入是否符合定义的模式,但不捕获所需的组。有关详细信息,请参见http://www.regular-expressions.info/recurse.html中的“匹配平衡结构”部分。

为了捕获组,我们可以将模式检查正则表达式转换为仅在字符串开头检查一次的正向先行断言((?:^(?=({\w+(?1)?})$)|\G(?!\A))),然后使用全局搜索捕获所有“单词”:

(?:^(?=({\w+(?1)?})$)|\G(?!\A)){(\w+)

"

现在 a, b, c 等都在第二个捕获组中。

正则表达式演示:https://regex101.com/r/2wsR10/2。PHP 演示:https://ideone.com/UKTfcm

解释:

"
  • (?: - 开始选择组
    • [第一种选择]:
      • ^ - 字符串开头
      • (?= - 开始正向预查
      • ({\w+(?1)?}) - 上述通用模式
      • $ - 字符串结尾
      • ) - 结束正向预查
    • | - 或者
    • [第二种选择]:
  • ) - 结束选择组
  • { - 打开大括号
  • (\w+) - 第二个组中捕获的“单词”。

Ruby对递归有不同的语法,正则表达式应为:

(?:^(?=({\w+\g<1>?})$)|\G(?!\A)){(\w+)

示例: http://rubular.com/r/jOJRhwJvR4


你的(?:^(?=({\w+(?1)?})$)|\G){(\w+)仍然匹配具有不平衡{...}的字符串。此外,为了使正则表达式兼容PCRE和Ruby的Onigmo,你可以用\g<1>替换(?1) - Wiktor Stribiżew
@Wiktor,感谢您的评论。在\G中添加(?!\ A)(?!^)应该可以解决这个问题。已更新答案。 - Dmitry Egorov

3
在.NET中,正则表达式可以1)检查平衡组并2)在组堆栈中为每个捕获组存储一个捕获集合。
使用以下正则表达式,您可以仅在整个以{开头且以}结尾的字符串包含平衡数量的打开/关闭花括号时提取每个{...}内的所有文本:
^{(?:(?<c>[^{}]+)|(?<o>){|(?<-o>)})*(?(o)(?!))}$

请查看正则表达式演示细节
  • ^ - 字符串的开始
  • { - 一个开放括号
  • (?: - 开始一组备选项:
    • (?<c>[^{}]+) - 将除了 {} 的1个或多个字符捕获到 "c" 组中
    • | - 或者
    • (?<o>{) - 匹配 { 并将一个值推送到 Group "o" 栈中
    • | - 或者
    • (?<-o>}) - 匹配 } 并从 Group "o" 栈中弹出一个值
  • )* - 结束备选项组,重复0次或多次
  • (?(o)(?!)) - 一个条件构造,检查 Group "o" 栈是否为空
  • } - 一个闭合的 }
  • $ - 字符串的结束。
请参考C#演示
var pattern = "^{(?:(?<c>[^{}]+)|(?<o>{)|(?<-o>}))*(?(o)(?!))}$";
var result = Regex.Matches("{a{bb{ccc{dd}}}}", pattern)
          .Cast<Match>().Select(p => p.Groups["c"].Captures)
          .ToList();

{a{bb{ccc{dd}}}} 的输出结果为 [a, bb, ccc, dd],而添加了开头的 { 后的 {{a{bb{ccc{dd}}}} 结果为空。


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