【问题标题】:Regular expression to match balanced parentheses匹配平衡括号的正则表达式
【发布时间】:2010-10-07 11:33:36
【问题描述】:

我需要一个正则表达式来选择两个外括号之间的所有文本。

示例:some text(text here(possible text)text(possible text(more text)))end text

结果:(text here(possible text)text(possible text(more text)))

【问题讨论】:

  • 这个问题很差,因为不清楚它在问什么。所有的答案都有不同的解释。 @DaveF 你能澄清一下这个问题吗?
  • 在这篇文章中回答:stackoverflow.com/questions/6331065/…

标签: regex


【解决方案1】:

我想添加此答案以供快速参考。随时更新。


.NET 正则表达式使用balancing groups

\((?>\((?<c>)|[^()]+|\)(?<-c>))*(?(c)(?!))\)

其中c 用作深度计数器。

Demo at Regexstorm.com


PCRE 使用recursive pattern

\((?:[^)(]+|(?R))*+\)

Demo at regex101;或者没有交替:

\((?:[^)(]*(?R)?)*+\)

Demo at regex101;或unrolled 以获得性能:

\([^)(]*+(?:(?R)[^)(]*)*+\)

Demo at regex101;模式粘贴在(?R),代表(?0)

Perl、PHP、Notepad++、 Rperl=TRUEPythonRegex package(?V1) 用于 Perl 行为。


Ruby 使用subexpression calls

使用 Ruby 2.0 \g&lt;0&gt; 可用于调用完整模式。

\((?>[^)(]+|\g<0>)*\)

Demo at Rubular; Ruby 1.9 仅支持capturing group recursion:

(\((?>[^)(]+|\g<1>)*\))

Demo at Rubularatomic grouping 自 Ruby 1.9.3 起)


JavaScript API :: XRegExp.matchRecursive

XRegExp.matchRecursive(str, '\\(', '\\)', 'g');

JS、Java 和其他无递归的正则表达式风格,最多 2 级嵌套:

\((?:[^)(]+|\((?:[^)(]+|\([^)(]*\))*\))*\)

Demo at regex101。深入nesting needs to be added 到模式。
在不平衡括号上更快地失败drop the + quantifier.


Java:一个有趣的idea using forward references by @jaytea


Reference - What does this regex mean?

【讨论】:

  • 当您使用所有格量词重复一个组时,使该组原子化是没有用的,因为该组中的所有回溯位置在每次重复时都会被删除。所以写(?&gt;[^)(]+|(?R))*+ 和写(?:[^)(]+|(?R))*+ 是一样的。下一个模式也是如此。关于展开的版本,您可以在此处放置一个所有格量词:[^)(]*+ 以防止回溯(如果没有右括号)。
  • 关于 Ruby 1.9 模式,您可以使用简单的非捕获组并将所有内容包含在一个原子组中:(?&gt;(?:[^)(]+|\g&lt;1&gt;)*)(其行为与所有格量词完全相同)。在 Ruby 2.x 中,所有格量词可用。
  • @CasimiretHippolyte 谢谢!我调整了 PCRE 模式,对于 Ruby 1.9,你的意思是整个模式是 like this 吗?请随时更新自己。我明白你的意思,但不确定是否有很大改进。
  • 感谢不使用递归的 JavaScript 示例。我可以在具有类似限制的 vbScript 中使用它。
  • 如果有人需要 .NET 的大括号版本:\{(?&gt;\{(?&lt;c&gt;)|[^{}]+|\}(?&lt;-c&gt;))*(?(c)(?!))\}
【解决方案2】:

正则表达式不适合这项工作,因为您正在处理嵌套结构,即递归。

但是有一个简单的算法可以做到这一点,我在in this answerprevious question 进行了更详细的描述。要点是编写代码来扫描字符串,保持一个开括号的计数器,这些开括号还没有被右括号匹配。当该计数器返回零时,您就知道您已到达最后的右括号。

【讨论】:

  • .NET 的实现有 [Balancing Group Definitions msdn.microsoft.com/en-us/library/… 允许这种事情。
  • 我不同意正则表达式是错误的工具,原因有几个。 1)大多数正则表达式实现都有一个可行的解决方案,如果不是完美的解决方案。 2) 通常,您试图在其他非常适合正则表达式的标准也在起作用的上下文中找到平衡的分隔符对。 3) 通常你将一个正则表达式交给一些只接受正则表达式的 API 并且你别无选择。
  • Regex 是适合这项工作的正确工具。这个答案是不对的。请参阅 rogal111 的回答。
  • 完全同意这个答案。尽管 regexp 中有一些递归实现,但它们等同于有限状态机,并且不支持使用嵌套结构,但上下文无关语法可以做到这一点。看看 Homsky 的形式语法层次结构。
【解决方案3】:

你可以使用regex recursion:

\(([^()]|(?R))*\)

【讨论】:

  • 一个例子在这里非常有用,我不能让它适用于像 "(1, (2, 3)) (4, 5)" 这样的东西。
  • @AndyHayden 这是因为 "(1, (2, 3)) (4, 5)" 有两组用空格隔开。将我的正则表达式与全局标志一起使用:/(([^()]|(?R))*)/g。这里是在线测试:regex101.com/r/lF0fI1/1
  • 上周我问了一个关于这个的问题stackoverflow.com/questions/26385984/recursive-pattern-in-regex
  • 在 .NET 4.5 中,我收到此模式的以下错误:Unrecognized grouping construct
  • 太棒了!这是正则表达式的一大特色。感谢您成为唯一真正回答问题的人。此外,那个 regex101 网站很不错。
【解决方案4】:
[^\(]*(\(.*\))[^\)]*

[^\(]* 匹配字符串开头不是左括号的所有内容,(\(.*\)) 捕获括在括号中的所需子字符串,[^\)]* 匹配结尾不是右括号的所有内容字符串。请注意,此表达式不会尝试匹配括号;一个简单的解析器(见dehmann's answer)会更适合。

【讨论】:

  • 类中的括号不需要转义。因为里面不是元字符。
  • 这个表达式失败了,比如“text(text)text(text)text”返回“(text)text(text)”。正则表达式不能算括号。
【解决方案5】:
(?<=\().*(?=\))

如果您想在两个 匹配 括号之间选择文本,那么您对正则表达式不走运。这是不可能的(*)

这个正则表达式只返回字符串中第一个左括号和最后一个右括号之间的文本。


(*) 除非您的正则表达式引擎具有balancing groups or recursion 之类的功能。支持此类功能的引擎数量正在缓慢增长,但它们仍然不是普遍可用的。

【讨论】:

  • 这是环视,或更准确地说是“零宽度前瞻/后瞻断言”。大多数现代正则表达式引擎都支持它们。
  • 根据 OP 的示例,他想在匹配中包含最外层的括号。这个正则表达式把它们扔掉了。
  • @Alan M:你是对的。但根据问题文本,他想要所有 between 最外层括号。选择您的选择。他说他已经尝试了几个小时,所以甚至没有考虑“包括最外面的括号在内的所有内容”作为意图,因为它是如此微不足道:“(.*)”。
  • @ghayes 答案是从 2009 年开始的。那是 很久 年前的事了;允许某种形式的递归的正则表达式引擎比现在更不常见(而且它们仍然非常不常见)。我会在我的回答中提到它。
【解决方案6】:

这个答案解释了为什么正则表达式不是这个任务的正确工具的理论限制。


正则表达式不能做到这一点。

正则表达式基于称为Finite State Automata (FSA) 的计算模型。顾名思义,FSA 只能记住当前状态,它没有关于以前状态的信息。

在上图中,S1 和 S2 是两个状态,其中 S1 是起始步和最终步。因此,如果我们尝试使用字符串 0110 ,转换过程如下:

      0     1     1     0
-> S1 -> S2 -> S2 -> S2 ->S1

在上述步骤中,当我们在第二个S2,即解析011001之后,FSA在01中没有关于前一个0的信息,因为它只能记住当前状态和下一个输入符号。

在上面的问题中,我们需要知道左括号的编号;这意味着它必须存储在某个地方。但是由于FSAs做不到,所以不能写正则表达式。

但是,可以编写一个算法来完成这项任务。算法通常属于Pushdown Automata (PDA)PDAFSA 高一级。 PDA 有一个额外的堆栈来存储一些额外的信息。 PDA 可以用来解决上述问题,因为我们可以'push' 堆栈中的左括号和'pop' 一旦我们遇到右括号。如果最后堆栈为空,则左括号和右括号匹配。否则不行。

【讨论】:

  • 在正则表达式 stackoverflow.com/questions/17003799/… regular-expressions.info/balancing.html 中可以进行推送和弹出操作
  • 这里有几个答案,证明是有可能的。
  • @Marco 这个答案从理论上讲正则表达式。现在许多正则表达式引擎不仅依赖这个理论模型,而且使用一些额外的内存来完成这项工作!
  • @JiříHerník:这些不是严格意义上的正则表达式:没有被 Kleene 定义为正则表达式。一些正则表达式引擎确实实现了一些额外的功能,使它们不仅仅解析常规语言
【解决方案7】:

实际上可以使用.NET 正则表达式来做到这一点,但这并不简单,所以请仔细阅读。

您可以阅读一篇不错的文章here。您可能还需要阅读 .NET 正则表达式。你可以开始阅读here

使用尖括号&lt;&gt;,因为它们不需要转义。

正则表达式如下所示:

<
[^<>]*
(
    (
        (?<Open><)
        [^<>]*
    )+
    (
        (?<Close-Open>>)
        [^<>]*
    )+
)*
(?(Open)(?!))
>

【讨论】:

    【解决方案8】:

    我也陷入了嵌套模式出现的这种情况。

    正则表达式是解决上述问题的正确方法。使用下面的模式

    '/(\((?>[^()]+|(?1))*\))/'
    

    【讨论】:

      【解决方案9】:

      这是最终的正则表达式:

      \(
      (?<arguments> 
      (  
        ([^\(\)']*) |  
        (\([^\(\)']*\)) |
        '(.*?)'
      
      )*
      )
      \)
      

      例子:

      input: ( arg1, arg2, arg3, (arg4), '(pip' )
      
      output: arg1, arg2, arg3, (arg4), '(pip'
      

      注意'(pip' 被正确地管理为字符串。 (在调节器中试用:http://sourceforge.net/projects/regulator/

      【讨论】:

      • 如果没有嵌套或者你只关心最里面的组,我喜欢这种技术。它不依赖递归。我能够使用它来提取包含括号的参数。我在Regex101 做了一个工作示例
      【解决方案10】:

      我编写了一个名为 balanced 的小型 JavaScript 库来帮助完成这项任务。你可以这样做

      balanced.matches({
          source: source,
          open: '(',
          close: ')'
      });
      

      你甚至可以做替换:

      balanced.replacements({
          source: source,
          open: '(',
          close: ')',
          replace: function (source, head, tail) {
              return head + source + tail;
          }
      });
      

      这是一个更复杂和互动的例子JSFiddle

      【讨论】:

        【解决方案11】:

        除了bobble bubble's answer,还有其他支持递归构造的正则表达式风格。

        Lua

        使用%b()%b{} / %b[] 用于大括号/方括号):

        • for s in string.gmatch("Extract (a(b)c) and ((d)f(g))", "%b()") do print(s) end(见demo

        Raku(前 Perl6)

        不重叠的多个平衡括号匹配:

        my regex paren_any { '(' ~ ')' [ <-[()]>+ || <&paren_any> ]* }
        say "Extract (a(b)c) and ((d)f(g))" ~~ m:g/<&paren_any>/;
        # => (「(a(b)c)」 「((d)f(g))」)
        

        重叠多个平衡括号匹配:

        say "Extract (a(b)c) and ((d)f(g))" ~~ m:ov:g/<&paren_any>/;
        # => (「(a(b)c)」 「(b)」 「((d)f(g))」 「(d)」 「(g)」)
        

        demo

        Python re 非正则表达式解决方案

        请参阅poke's answer 以获取How to get an expression between balanced parentheses

        Java 可定制的非正则表达式解决方案

        这是一个可定制的解决方案,允许在 Java 中使用单字符文字分隔符:

        public static List<String> getBalancedSubstrings(String s, Character markStart, 
                                         Character markEnd, Boolean includeMarkers) 
        
        {
                List<String> subTreeList = new ArrayList<String>();
                int level = 0;
                int lastOpenDelimiter = -1;
                for (int i = 0; i < s.length(); i++) {
                    char c = s.charAt(i);
                    if (c == markStart) {
                        level++;
                        if (level == 1) {
                            lastOpenDelimiter = (includeMarkers ? i : i + 1);
                        }
                    }
                    else if (c == markEnd) {
                        if (level == 1) {
                            subTreeList.add(s.substring(lastOpenDelimiter, (includeMarkers ? i + 1 : i)));
                        }
                        if (level > 0) level--;
                    }
                }
                return subTreeList;
            }
        }
        

        示例用法:

        String s = "some text(text here(possible text)text(possible text(more text)))end text";
        List<String> balanced = getBalancedSubstrings(s, '(', ')', true);
        System.out.println("Balanced substrings:\n" + balanced);
        // => [(text here(possible text)text(possible text(more text)))]
        

        【讨论】:

        【解决方案12】:

        使用 Ruby 的正则表达式(1.9.3 或以上版本):

        /(?<match>\((?:\g<match>|[^()]++)*\))/
        

        Demo on rubular

        【讨论】:

          【解决方案13】:

          答案取决于您是需要匹配匹配的括号组,还是仅匹配输入文本中的第一个打开到最后一个关闭。

          如果您需要匹配匹配的嵌套括号,那么您需要的不仅仅是正则表达式。 - 见@dehmann

          如果只是第一次打开到最后一次关闭,请参阅@Zach

          决定你想做什么:

          abc ( 123 ( foobar ) def ) xyz ) ghij
          

          您需要决定在这种情况下您的代码需要匹配什么。

          【讨论】:

          • 这不是答案。
          • 是的,问题的改变要求应该作为评论给出,
          【解决方案14】:
          """
          Here is a simple python program showing how to use regular
          expressions to write a paren-matching recursive parser.
          
          This parser recognises items enclosed by parens, brackets,
          braces and <> symbols, but is adaptable to any set of
          open/close patterns.  This is where the re package greatly
          assists in parsing. 
          """
          
          import re
          
          
          # The pattern below recognises a sequence consisting of:
          #    1. Any characters not in the set of open/close strings.
          #    2. One of the open/close strings.
          #    3. The remainder of the string.
          # 
          # There is no reason the opening pattern can't be the
          # same as the closing pattern, so quoted strings can
          # be included.  However quotes are not ignored inside
          # quotes.  More logic is needed for that....
          
          
          pat = re.compile("""
              ( .*? )
              ( \( | \) | \[ | \] | \{ | \} | \< | \> |
                                     \' | \" | BEGIN | END | $ )
              ( .* )
              """, re.X)
          
          # The keys to the dictionary below are the opening strings,
          # and the values are the corresponding closing strings.
          # For example "(" is an opening string and ")" is its
          # closing string.
          
          matching = { "(" : ")",
                       "[" : "]",
                       "{" : "}",
                       "<" : ">",
                       '"' : '"',
                       "'" : "'",
                       "BEGIN" : "END" }
          
          # The procedure below matches string s and returns a
          # recursive list matching the nesting of the open/close
          # patterns in s.
          
          def matchnested(s, term=""):
              lst = []
              while True:
                  m = pat.match(s)
          
                  if m.group(1) != "":
                      lst.append(m.group(1))
          
                  if m.group(2) == term:
                      return lst, m.group(3)
          
                  if m.group(2) in matching:
                      item, s = matchnested(m.group(3), matching[m.group(2)])
                      lst.append(m.group(2))
                      lst.append(item)
                      lst.append(matching[m.group(2)])
                  else:
                      raise ValueError("After <<%s %s>> expected %s not %s" %
                                       (lst, s, term, m.group(2)))
          
          # Unit test.
          
          if __name__ == "__main__":
              for s in ("simple string",
                        """ "double quote" """,
                        """ 'single quote' """,
                        "one'two'three'four'five'six'seven",
                        "one(two(three(four)five)six)seven",
                        "one(two(three)four)five(six(seven)eight)nine",
                        "one(two)three[four]five{six}seven<eight>nine",
                        "one(two[three{four<five>six}seven]eight)nine",
                        "oneBEGINtwo(threeBEGINfourENDfive)sixENDseven",
                        "ERROR testing ((( mismatched ))] parens"):
                  print "\ninput", s
                  try:
                      lst, s = matchnested(s)
                      print "output", lst
                  except ValueError as e:
                      print str(e)
              print "done"
          

          【讨论】:

            【解决方案15】:

            您需要第一个和最后一个括号。使用这样的东西:

            str.indexOf('('); - 它会给你第一次出现

            str.lastIndexOf(')'); - 最后一个

            所以你需要一个字符串,

            String searchedString = str.substring(str1.indexOf('('),str1.lastIndexOf(')');
            

            【讨论】:

              【解决方案16】:

              由于 js 正则表达式不支持递归匹配,我无法使平衡括号匹配工作。

              所以这是一个简单的 javascript for 循环版本,将“method(arg)”字符串转换为数组

              push(number) map(test(a(a()))) bass(wow, abc)
              $$(groups) filter({ type: 'ORGANIZATION', isDisabled: { $ne: true } }) pickBy(_id, type) map(test()) as(groups)
              
              const parser = str => {
                let ops = []
                let method, arg
                let isMethod = true
                let open = []
              
                for (const char of str) {
                  // skip whitespace
                  if (char === ' ') continue
              
                  // append method or arg string
                  if (char !== '(' && char !== ')') {
                    if (isMethod) {
                      (method ? (method += char) : (method = char))
                    } else {
                      (arg ? (arg += char) : (arg = char))
                    }
                  }
              
                  if (char === '(') {
                    // nested parenthesis should be a part of arg
                    if (!isMethod) arg += char
                    isMethod = false
                    open.push(char)
                  } else if (char === ')') {
                    open.pop()
                    // check end of arg
                    if (open.length < 1) {
                      isMethod = true
                      ops.push({ method, arg })
                      method = arg = undefined
                    } else {
                      arg += char
                    }
                  }
                }
              
                return ops
              }
              
              // const test = parser(`$$(groups) filter({ type: 'ORGANIZATION', isDisabled: { $ne: true } }) pickBy(_id, type) map(test()) as(groups)`)
              const test = parser(`push(number) map(test(a(a()))) bass(wow, abc)`)
              
              console.log(test)
              
              

              结果是这样的

              [ { method: 'push', arg: 'number' },
                { method: 'map', arg: 'test(a(a()))' },
                { method: 'bass', arg: 'wow,abc' } ]
              
              [ { method: '$$', arg: 'groups' },
                { method: 'filter',
                  arg: '{type:\'ORGANIZATION\',isDisabled:{$ne:true}}' },
                { method: 'pickBy', arg: '_id,type' },
                { method: 'map', arg: 'test()' },
                { method: 'as', arg: 'groups' } ]
              

              【讨论】:

                【解决方案17】:

                虽然很多答案以某种形式提到这一点,说正则表达式不支持递归匹配等,但主要原因在于计算理论的根源。

                {a^nb^n | n&gt;=0} is not regular 形式的语言。正则表达式只能匹配构成常规语言集一部分的内容。

                阅读更多@here

                【讨论】:

                  【解决方案18】:

                  我没有使用正则表达式,因为它很难处理嵌套代码。所以这个 sn-p 应该可以让你用平衡的括号抓取代码段:

                  def extract_code(data):
                      """ returns an array of code snippets from a string (data)"""
                      start_pos = None
                      end_pos = None
                      count_open = 0
                      count_close = 0
                      code_snippets = []
                      for i,v in enumerate(data):
                          if v =='{':
                              count_open+=1
                              if not start_pos:
                                  start_pos= i
                          if v=='}':
                              count_close +=1
                              if count_open == count_close and not end_pos:
                                  end_pos = i+1
                          if start_pos and end_pos:
                              code_snippets.append((start_pos,end_pos))
                              start_pos = None
                              end_pos = None
                  
                      return code_snippets
                  
                  

                  我用它从文本文件中提取代码 sn-ps。

                  【讨论】:

                    【解决方案19】:

                    这并没有完全解决 OP 问题,但我认为它可能对一些来这里搜索嵌套结构正则表达式的人有用:

                    在 javascript 中从函数字符串(具有嵌套结构)解析参数

                    匹配结构如:

                    • 匹配括号、方括号、圆括号、单引号和双引号

                    Here you can see generated regexp in action

                    /**
                     * get param content of function string.
                     * only params string should be provided without parentheses
                     * WORK even if some/all params are not set
                     * @return [param1, param2, param3]
                     */
                    exports.getParamsSAFE = (str, nbParams = 3) => {
                        const nextParamReg = /^\s*((?:(?:['"([{](?:[^'"()[\]{}]*?|['"([{](?:[^'"()[\]{}]*?|['"([{][^'"()[\]{}]*?['")}\]])*?['")}\]])*?['")}\]])|[^,])*?)\s*(?:,|$)/;
                        const params = [];
                        while (str.length) { // this is to avoid a BIG performance issue in javascript regexp engine
                            str = str.replace(nextParamReg, (full, p1) => {
                                params.push(p1);
                                return '';
                            });
                        }
                        return params;
                    };
                    

                    【讨论】:

                      【解决方案20】:

                      这可能有助于匹配平衡括号。

                      \s*\w+[(][^+]*[)]\s*
                      

                      【讨论】:

                        【解决方案21】:

                        这个也有用

                        re.findall(r'\(.+\)', s)
                        

                        【讨论】:

                          猜你喜欢
                          • 2011-12-19
                          • 2015-11-04
                          • 1970-01-01
                          • 1970-01-01
                          • 2022-10-25
                          相关资源
                          最近更新 更多