匹配平衡括号的正则表达式

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

< p >例子:< br / > START_TEXT(text here(possible text)text(possible text(more text)))END_TXT < br / > ^ ^ < / p > < p >结果:< br / > (text here(possible text)text(possible text(more text))) < / p >
466198 次浏览
[^\(]*(\(.*\))[^\)]*

[^\(]*匹配字符串开头不是右括号的所有内容,(\(.*\))捕获括在括号中的所需子字符串,而[^\)]*匹配字符串末尾不是右括号的所有内容。注意,这个表达式不会试图匹配括号;一个简单的解析器(参见dehmann的回答)将更适合于此。

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

如果你想要在两个匹配括号之间选择文本,那么使用正则表达式你就不走运了。这是不可能的__abc1。

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


除非你的正则表达式引擎有像平衡组递归这样的特性。支持这些特性的引擎的数量正在缓慢增长,但它们仍然不是普遍可用的。

正则表达式是一个错误的工具,因为你正在处理嵌套结构,即递归。

但是有一个简单的算法可以做到这一点,我更详细地描述了在这个答案中前一个问题。其要点是编写代码扫描字符串,并对尚未与闭括号匹配的开括号保持计数器。当计数器返回0时,您就知道已经到达了最后的右括号。

答案取决于您是需要匹配括号的匹配集,还是只需要匹配输入文本中的第一个开始到最后一个结束的括号。

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

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

决定你想要发生什么:

abc ( 123 ( foobar ) def ) xyz ) ghij

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

实际上,使用. net正则表达式是可以做到这一点的,但它并不是微不足道的,所以请仔细阅读。

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

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

正则表达式是这样的:

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

这是最终的正则表达式:

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


)*
)
\)

例子:

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


output: arg1, arg2, arg3, (arg4), '(pip'
注意'(pip'被正确地管理为字符串。 (try in regulator: http://sourceforge.net/projects/regulator/)

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

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

Demo on rubular

你可以使用regex递归:

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

我写了一个叫做平衡的JavaScript库来帮助完成这个任务。你可以这样做

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

你甚至可以做替换:

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

下面是一个更复杂和交互式的例子JSFiddle

我想添加这个答案,以便快速参考。请随时更新。


net正则表达式使用平衡组:

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

其中c被用作深度计数器。

Demo at Regexstorm.com


PCRE使用递归模式:

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

在regex101上进行演示;或无交替的:

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

在regex101上进行演示;或展开的性能:

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

在regex101上进行演示;模式被粘贴到(?R),它表示(?0)

< p > Perl, PHP, notepad++, R: perl = TRUE Python: PyPI正则表达式模块 Perl的行为 (?V1)
(新版本的PyPI正则表达式包已经违约 to this→DEFAULT_VERSION = VERSION1)


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');

Java:一个有趣的@jaytea使用前向引用的想法


没有递归最多3层嵌套:
(JS, Java和其他类型的正则表达式)

为了防止失控的 如果不平衡,将*只放在最里面的[)(]上。

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

在regex101上进行演示;或展开以获得更好的性能(首选)。

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

在regex101上进行演示;
.
.


Reference -这个正则表达式是什么意思?< / > < br >

除了泡泡泡泡的答案之外,还有其他类型的正则表达式支持递归结构。

Lua

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

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

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

看到演示

Python re非正则解

有关如何在平衡括号之间得到一个表达式,请参见戳的回答

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

你需要第一个和最后一个括号。可以这样说:

str.indexOf (' (');-它会给你第一次发生

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

所以你需要一个字符串,

String searchedString = str.substring(str1.indexOf('('),str1.lastIndexOf(')');
"""
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"

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


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

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

FSA

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

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

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

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

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

这个也有用

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

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

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

匹配如下结构:
从函数字符串中解析参数 < / p >

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

在这里您可以看到生成的regexp在操作

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

因为js regex不支持递归匹配,我不能使平衡括号匹配工作。

这是一个简单的javascript循环版本,将“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' } ]

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

{a^nb^n | n>=0} is not regular形式的语言。Regex只能匹配构成常规语言集一部分的东西。

阅读更多@ 在这里

我没有使用正则表达式,因为它很难处理嵌套代码。因此,这个代码片段应该能够允许你抓取带有平衡括号的代码段:

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


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

在处理嵌套模式和正则表达式是解决这类问题的正确工具时,我也陷入了这种情况。

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

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

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