正则表达式匹配平衡括号

时间:2009-02-13 15:49:21

标签: regex

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

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

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

21 个答案:

答案 0 :(得分:119)

正则表达式是该作业的错误工具,因为您正在处理嵌套结构,即递归。

但是有一个简单的算法可以做到这一点,我将in this answer描述为previous question

答案 1 :(得分:92)

您可以使用regex recursion

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

答案 2 :(得分:83)

我想为快速参考添加此答案。随意更新。

.NET正则表达式使用balancing groups

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

其中c用作深度计数器。

Demo at Regexstorm.com

使用recursive pattern

PCRE

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

Demo at regex101;或者没有交替:

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

Demo at regex101;或unrolled表现:

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

Demo at regex101;该模式粘贴在代表(?R)的{​​{1}}上。

Perl,PHP,Notepad ++, R perl=TRUE Python Regex package {{1}对于Perl行为。

使用subexpression calls

Ruby

使用Ruby 2.0 (?0)可用于调用完整模式。

(?V1)

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

\g<0>

Demo at Rubular(自Ruby 1.9.3后atomic grouping

JavaScript API :: XRegExp.matchRecursive

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

JS,Java和其他正则表达式,没有递归,最多可达2级嵌套:

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

Demo at regex101。模式更深nesting needs to be added 在不平衡的括号drop the + quantifier.

上更快地失败

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

<子> Reference - What does this regex mean?

答案 3 :(得分:27)

[^\(]*(\(.*\))[^\)]*

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

答案 4 :(得分:15)

(?<=\().*(?=\))

如果要在两个匹配的括号中选择文本,则表示您对正则表达式不满意。这是不可能的(*)

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


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

答案 5 :(得分:11)

实际上可以使用.NET正则表达式来完成它,但它并不简单,所以请仔细阅读。

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

使用了尖括号<>,因为它们不需要转义。

正则表达式如下所示:

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

答案 6 :(得分:6)

这个答案解释了为什么正则表达式不适合这项任务的理论限制。

正则表达式无法执行此操作。

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

FSA

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

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

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

在上述问题中,我们需要知道左括号的否;这意味着必须在某个地方存储。但由于01无法做到这一点,因此无法编写正则表达式。

但是,可以编写一个算法来实现目标。算法通常属于FSAsPushdown Automata (PDA)PDA高出一级。 PDA有一个额外的堆栈来存储东西。 PDA可以用来解决上述问题,因为我们可以FSA&#39;堆栈中的左括号和&#39; push&#39;一旦我们遇到一个右括号。如果最后,stack为空,则打开括号和右括号匹配。否则不是。

答案 7 :(得分:3)

这是权威的正则表达式:

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

)*
)
\)

示例:

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

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

请注意'(pip'已正确管理为字符串。 (在监管机构中试过:http://sourceforge.net/projects/regulator/

答案 8 :(得分:2)

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

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

Demo on rubular

答案 9 :(得分:2)

我编写了一个名为balanced的小型JavaScript库来帮助完成此任务。你可以通过

完成这个
balanced.matches({
    source: source,
    open: '(',
    close: ')'
});

你甚至可以做替换:

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

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

答案 10 :(得分:2)

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

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

str.lastIndexOf(&#39;)&#39); - 最后一个

所以你需要一个字符串,

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

答案 11 :(得分:1)

添加到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

<强> 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 answerHow 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 :(得分:1)

在这种情况下,我也陷于嵌套模式的出现。

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

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

答案 13 :(得分:0)

"""
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"

答案 14 :(得分:0)

这也有用

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

答案 15 :(得分:0)

因为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' } ]

答案 16 :(得分:0)

我不使用正则表达式,因为它很难处理嵌套代码。因此,此代码段应能够使您使用平衡的方括号来抓取代码段:

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

我用它来从文本文件中提取代码片段。

答案 17 :(得分:0)

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

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

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

决定你想要发生什么:

abc ( 123 ( foobar ) def ) xyz ) ghij

在这种情况下,您需要确定代码需要匹配的内容。

答案 18 :(得分:-1)

这对某些人可能有用:

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

匹配以下结构:
[basic.fundamentals]/6

  • 匹配方括号,方括号,括号,单引号和双引号

Parse parmeters from function string

/**
 * 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;
};

这不能完全解决OP问题,但对于某些来这里搜索嵌套结构正则表达式的人来说可能有用。

答案 19 :(得分:-1)

尽管有这么多答案以某种形式提到正则表达式不支持递归匹配等,但这主要是由于计算理论的根源。

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

了解更多@ here

答案 20 :(得分:-1)

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

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