正则表达式以匹配括号


290

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

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

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


3
这个问题很糟糕,因为不清楚它在问什么。所有答案对它的解释都不同。@DaveF能否请您澄清问题?
马特·芬威克

1

Answers:


144

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

但是有一个简单的算法可以做到这一点,我上一个问题的答案中对此进行了描述。


15
.NET的实现具有[Balancing Group Definitions msdn.microsoft.com/en-us/library / ...],它允许这种事情。
卡尔·G

22
由于某些原因,我不同意正则表达式是错误的工具。1)大多数正则表达式实现都有一个可行的解决方案,即使不是完美的解决方案。2)通常,您在试图使用其他非常适合正则表达式的条件的情况下,试图找到平衡的定界符对。3)通常,您会将正则表达式传递到一些仅接受正则表达式的API中,而您别无选择。
肯尼思·巴尔特里尼克


20
正则表达式是正确的工作工具。这个答案不对。请参阅rogal111的答案。
安德鲁(Andrew)

4
完全同意答案。尽管在regexp中有一些递归实现,但它们等同于有限状态机,并且不支持使用嵌套结构,但是上下文无关文法可以做到这一点。看看霍姆斯基的形式语法语法。
尼克·罗兹

138

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


.NET正则表达式使用平衡组

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

哪里 c用作深度计数器。

在Regexstorm.com上进行演示


PCRE使用递归模式

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

regex101上的演示;或无交替:

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

regex101上的演示;或为提高性能而展开

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

regex101上的演示;图案粘贴在(?R)代表(?0)

Perl,PHP,Notepad ++, Rperl = TRUEPython:带有用于Perl行为的Regex软件包(?V1)


Ruby使用子表达式调用

使用Ruby 2.0 \g<0>可以用来调用完整模式。

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

演示在Rubular ; Ruby 1.9仅支持捕获组递归

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

Rubular上的演示  (自Ruby 1.9.3起的原子分组


JavaScript  API :: XRegExp.matchRecursive

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

JS,Java和其他正则表达式版本,无需递归最多嵌套2个级别:

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

regex101上的演示需要将更深层的嵌套添加到模式中。
要在不平衡的括号上更快失败,请删除该+量词。


Java使用@jaytea的正向引用的有趣想法


参考-此正则表达式是什么意思?


1
当您用所有格量词重复一个组时,使该组原子化是没有用的,因为该组中的所有回溯位置在每次重复时都会被删除。所以写作和写作(?>[^)(]+|(?R))*+是一样的(?:[^)(]+|(?R))*+。下一个模式也一样。关于展开的版本,您可以在此处放置一个所有格量词:[^)(]*+防止回溯(以防万一)。
卡西米尔和希波吕特

关于Ruby 1.9的模式,而不是使重复组原子(即具有当有许多嵌套括号有限的利息(...(..)..(..)..(..)..(..)..))目标字符串中),你可以使用一个简单的非捕获组和封闭所有的原子团:(?>(?:[^)(]+|\g<1>)*)(这就像所有格量词一样)。在Ruby 2.x中,所有格量词可用。
Casimir et Hippolyte,

@CasimiretHippolyte谢谢!我调整了PCRE模式,对于Ruby 1.9,您是说整个模式都是这样吗?请随时更新自己。我理解您的意思,但不确定是否有很大的改善。
泡泡

117

您可以使用正则表达式递归

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

3
一个例子在这里真的很有用,我无法使它用于“(1,(2,3))(4,5)”之类的事情。
安迪·海登

4
@AndyHayden这是因为“(1,(2,3))(4,5)”有两个用空格隔开的组。将我的正则表达式与全局标志一起使用:/(([[^()] |(?R))*)/ g。这是在线测试:regex101.com/r/lF0fI1/1
rogal111


7
在.NET 4.5中,对于此模式,出现以下错误:Unrecognized grouping construct
2015年

3
太棒了!这是正则表达式的重要功能。感谢您是唯一实际回答该问题的人。另外,该regex101网站很不错。
安德鲁(Andrew)

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

[^\(]*匹配字符串开头的不是(\(.*\))左括号的[^\)]*所有内容,捕获括号中所需的子字符串,并匹配字符串末尾的不是右括号的所有内容。请注意,此表达式不尝试与方括号匹配;一个简单的解析器(请参阅dehmann的答案)将更适合于此


类内的括号不需要转义。由于它不是元字符。
何塞·莱亚尔

10
此expr无法处理类似“ text(text)text(text)text”之类的返回“(text)text(text)”的事情。正则表达式不能计算括号。
Christian Klauser

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

如果要在两个匹配的括号之间选择文本,则不适合使用正则表达式。这是不可能的(*)

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


(*)除非您的正则表达式引擎具有平衡组递归之类的功能。支持此类功能的引擎数量正在缓慢增长,但是仍然不是很普遍。


“ <=”和“ =“”是什么意思?此表达式定位于哪个正则表达式引擎?
Christian Klauser

1
这是环顾四周,或更准确地说是“零宽度先行查找/后向声明”。大多数现代正则表达式引擎都支持它们。
Tomalak

根据OP的示例,他希望在比赛中加入最外面的球。此正则表达式将其丢弃。
艾伦·摩尔

1
@Alan M:你是对的。但是根据问题文本,他希望所有最外层之间的东西。选择您的选择。他说他已经尝试了几个小时,所以甚至都没有考虑“包括最外层括号在内的所有东西”的意图,因为它是如此琐碎:“(。*)”。
Tomalak

3
@ghayes答案是从2009年开始这是一个很长的时间之前; 允许某种形式的递归的正则表达式引擎比现在更不常见了(并且仍然很不常见)。我会在回答中提及。
Tomalak

14

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


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

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

金融服务管理局

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

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

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

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

但是,可以编写算法来完成此任务。算法通常属于Pushdown Automata (PDA)PDA是的上一级FSA。PDA有一个额外的堆栈来存储一些其他信息。PDA可用于解决上述问题,因为我们可以push在堆栈中“打开括号pop”,并在遇到闭合括号后“”。如果最后堆栈是空的,则打开括号和关闭括号匹配。否则不行。



1
这里有几个答案,可以证明是可能的。
吉日Herník

1
@Marco这个答案从理论角度讨论了正则表达式。如今,许多正则表达式引擎不仅每天都依赖于这种理论模型,而且还使用一些额外的内存来完成这项工作!
musibs

@JiříHerník:从严格意义上讲,它们不是正则表达式:Kleene未将其定义为正则表达式。某些正则表达式引擎确实实现了一些额外的功能,使它们不仅解析正则语言,而且还解析更多内容。
Willem Van Onsem

12

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

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

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

正则表达式如下所示:

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

4

这是权威的正则表达式:

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

)*
)
\)

例:

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

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

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


4

我已经编写了一个称为Balanced的小JavaScript库来帮助完成此任务。您可以通过执行此操作

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

您甚至可以进行替换:

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

这是一个更复杂且更具交互性的示例JSFiddle


4

除了气泡的答案外,还有其他支持递归构造的正则表达式。

a

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

  • for s in string.gmatch("Extract (a(b)c) and ((d)f(g))", "%b()") do print(s) end(请参阅演示

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)」)

参见演示

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

请参阅poke的答案,了解如何在平衡的括号之间获得一个表达式

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)))]

请参阅在线Java演示以获取证明它可以与多个匹配项一起使用的证明。
WiktorStribiżew17年

3

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

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

演示在rubular


3

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

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

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

所以你需要一个字符串,

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

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

0

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

如果需要匹配匹配的嵌套方括号,则除了正则表达式外,还需要其他内容。-见@dehmann

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

决定要发生的事情:

abc ( 123 ( foobar ) def ) xyz ) ghij

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


3
这不是答案。
艾伦·摩尔

是的,要求对问题进行更改的要求应以评论的形式给出,
Gangnus 2015年

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

0

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

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

在这里阅读更多


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

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


0

在这种情况下,我也陷入了嵌套模式的困境。

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

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


-1

这对某些人可能有用:

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

匹配结构,例如:
从函数字符串解析参数

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

在这里,您可以看到正在生成的正则表达式

/**
 * 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问题,但我对一些来这里搜索嵌套结构的regexp可能有用。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.