示例:
some text(text here(possible text)text(possible text(more text)))end text
结果:
(text here(possible text)text(possible text(more text)))
#1 楼
正则表达式是错误的工具,因为您正在处理嵌套结构,即递归。但是有一个简单的算法可以做到这一点,我在上一个问题的答案中对此进行了描述。
评论
.NET的实现具有[Balancing Group Definitionsmsdn.microsoft.com/zh-cn/library/…,它允许这种事情。
–卡尔G
2010年6月13日在4:08
由于某些原因,我不同意正则表达式是错误的工具。 1)大多数正则表达式实现都有一个可行的解决方案,即使不是完美的解决方案。 2)通常,您在试图使用其他非常适合正则表达式的条件的情况下,试图找到平衡的定界符对。 3)通常,您会将正则表达式传递到某些仅接受正则表达式的API中,而您别无选择。
–肯尼斯·巴尔特里尼克(Kenneth Baltrinic)
2014年5月2日,3:31
这是Frank算法的Javascript实现
– pilau
2014年11月23日11:00
正则表达式是正确的工作工具。这个答案不对。请参阅rogal111的答案。
–安德鲁(Andrew)
15/12/26在2:48
完全同意答案。尽管在regexp中有一些递归实现,但它们等同于有限状态机,并且不支持使用嵌套结构,但是上下文无关文法可以做到这一点。看看霍姆斯基的形式语法语法。
–尼克·罗兹(Nick Roz)
16-4-20在10:52
#2 楼
我想添加此答案作为快速参考。随时进行更新。.NET正则表达式使用平衡组。
\((?>\((?<c>)|[^()]+|\)(?<-c>))*(?(c)(?!))\)
其中
c
用作深度计数器。 Regexstorm.com上的演示
堆栈溢出:使用RegEx平衡匹配括号
Wes令人费解的博客:使用.NET Regular匹配平衡结构表达式
Greg Reinacker的博客:正则表达式中的嵌套构造
使用递归模式进行PCRE。
\((?:[^)(]+|(?R))*+\)
演示在regex101;或不作更改:
\((?:[^)(]*(?R)?)*+\)
regex101上的演示;或为了表现而展开:
\([^)(]*+(?:(?R)[^)(]*)*+\)
regex101上的演示;该模式粘贴在代表
(?R)
的(?0)
上。Perl,PHP,Notepad ++,R:perl = TRUE,Python:带有
(?V1)
的Regex软件包用于Perl行为。使用子表达式调用的Ruby。
在Ruby 2.0中,
\g<0>
可以用来调用完整模式。\((?>[^)(]+|\g<0>)*\)
在Rubular的演示; Ruby 1.9仅支持捕获组递归:
(\((?>[^)(]+|\g<1>)*\))
Demo的演示(自Ruby 1.9.3起的原子分组)
JavaScript API :: XRegExp.matchRecursive
XRegExp.matchRecursive(str, '\(', '\)', 'g');
没有递归的JS,Java和其他正则表达式版本最多可嵌套2级:
\((?:[^)(]+|\((?:[^)(]+|\([^)(]*\))*\))*\)
regex101中的演示。需要在模式中添加更深层的嵌套。
要在不平衡括号中更快地失败,请删除
+
量词。Java:使用@jaytea的前向引用的有趣概念。
参考-正则表达式是什么意思?
rexegg.com-递归正则表达式
Regular-Expressions.info-正则表达式递归
评论
当您用所有格修饰符重复一个组时,使该组原子化是没有用的,因为该组中的所有回溯位置在每次重复时都会被删除。因此,写作(?> [^)(] + |(?R))* +与写作(?:[^)(] + |(?R))* +相同。下一个模式也一样。关于展开的版本,您可以在此处放置一个所有格量词:[^)(] * +,以防止回溯(如果没有右括号)。
– Casimir et Hippolyte
19年6月24日在21:03
关于Ruby 1.9模式,而不是使重复的组成为原子(当嵌套的括号很多时(...(......(..)..(..)..(。 )..))在主题字符串中),您可以使用一个简单的非捕获组并将所有组括在一个原子组中:(?>(?:[^)(] + | \ g <1>)*)(这就像所有格量词一样)。在Ruby 2.x中,所有格量词是可用的。
– Casimir et Hippolyte
19年6月24日在21:13
@CasimiretHippolyte谢谢!我调整了PCRE模式,对于Ruby 1.9,您是说整个模式都是这样吗?请随时更新自己。我理解您的意思,但是不确定是否有很大的改善。
–泡泡
19-6-25在9:13
感谢您不使用递归的JavaScript示例。我可以在具有类似限制的vbScript中使用此功能。
–本
9月9日在1:33
谢谢,这个答案真是棒极了,所有可能的演示工作都使我可以选择我使用的那个(regex101.org)。我非常感谢你的工作
–TomášVotruba
9月13日17:20
#3 楼
您可以使用正则表达式递归:\(([^()]|(?R))*\)
评论
一个例子在这里真的很有用,我无法使它适用于“(1,(2,3))(4,5)”之类的事情。
–安迪·海登(Andy Hayden)
14-10-15在0:01
@AndyHayden这是因为“((1,(2,3))(4,5)””具有两个用空格分隔的组。将我的regexp与全局标志一起使用:/(([[^()] |(?R))*)/ g。这是在线测试:regex101.com/r/lF0fI1/1
–rogal111
2014-10-23 9:45
我上周问了一个关于这个的问题stackoverflow.com/questions/26385984/recursive-pattern-in-regex
–安迪·海登(Andy Hayden)
14-10-23在17:20
在.NET 4.5中,对于此模式,出现以下错误:无法识别的分组构造。
– Nam
2015年6月28日在0:16
太棒了!这是正则表达式的重要功能。感谢您是唯一实际回答该问题的人。另外,该regex101网站很不错。
–安德鲁(Andrew)
15/12/26在2:47
#4 楼
[^\(]*(\(.*\))[^\)]*
[^\(]*
匹配字符串开头不为左括号的所有内容,(\(.*\))
捕获括号内所需的子字符串,而[^\)]*
匹配不为结尾的右括号的所有内容字符串。请注意,此表达式不尝试与方括号匹配;一个简单的解析器(请参阅dehmann的答案)将更适合于此。 评论
类内的括号不需要转义。由于它不是元字符。
–JoséLeal
09年2月13日在15:59
此expr无法处理类似“ text(text)text(text)text”之类的返回“(text)text(text)”的事情。正则表达式不能计算括号。
–克里斯蒂安·克劳瑟(Christian Klauser)
09年2月13日在16:02
#5 楼
(?<=\().*(?=\))
如果要在两个匹配的括号之间选择文本,则不适合使用正则表达式。这是不可能的(*)。
此正则表达式仅返回字符串中第一个开括号和最后一个闭括号之间的文本。
(*)除非您的正则表达式引擎具有诸如平衡组或递归之类的功能。支持此类功能的引擎数量正在缓慢增长,但是仍然不是很普遍。
评论
“ <=”和“ =“”是什么意思?此表达式定位于哪个正则表达式引擎?
–克里斯蒂安·克劳瑟(Christian Klauser)
09年2月13日在15:58
这是环顾四周,或更准确地说是“零宽度先行查找/后向声明”。大多数现代正则表达式引擎都支持它们。
– Tomalak
09年2月13日在16:01
根据OP的示例,他希望在比赛中加入最外面的球。此正则表达式将其丢弃。
–艾伦·摩尔
09年2月15日在5:09
@Alan M:你是对的。但是根据问题文本,他希望所有最外层之间的东西。选择您的选择。他说他已经尝试了几个小时,所以甚至都没有考虑“包括最外层括号在内的所有内容”的意图,因为它是如此琐碎:“(。*)”。
– Tomalak
09年2月15日在10:29
@ghayes答案是从2009年开始的。允许某种形式的递归的正则表达式引擎比现在更不常见了(并且仍然非常不常见)。我会在回答中提及。
– Tomalak
15年1月12日在7:54
#6 楼
这个答案解释了为什么正则表达式不是此任务的正确工具的理论限制。正则表达式不能做到这一点。
正则表达式基于称为
Finite State Automata (FSA)
的计算模型。顾名思义,一个FSA
只能记住当前状态,而没有有关先前状态的信息。在上图中,S1和S2是两个指出S1是开始和最后一步。因此,如果我们尝试使用字符串
0110
,则转换如下: 0 1 1 0
-> S1 -> S2 -> S2 -> S2 ->S1
S2中的前一个
01
的信息,因为它只能记住当前状态和下一个输入符号。在上述问题中,我们需要知道开括号的编号;这意味着它必须存储在某个地方。但是由于
0110
无法做到这一点,因此无法编写正则表达式。但是,可以编写算法来完成此任务。算法通常属于
0
。 01
比FSAs
高一级。 PDA有一个额外的堆栈来存储一些其他信息。 PDA可用于解决上述问题,因为我们可以在堆栈中用“ Pushdown Automata (PDA)
”打开括号,并在遇到闭合括号时“ PDA
”。如果在最后,堆栈为空,则打开括号和关闭括号匹配。否则不行。 评论
可以在regexpstackoverflow.com/questions/17003799/…中进行推送和弹出操作... regular-expressions.info/balancing.html
–马可
18年8月23日在19:35
这里有几个答案,可以证明是可能的。
–吉日·赫尔尼克(JiříHerník)
18-09-20在10:48
@Marco这个答案从理论角度讨论正则表达式。如今,许多正则表达式引擎不仅每天都依赖于这种理论模型,而且还使用一些额外的内存来完成这项工作!
– musibs
19年6月3日在2:07
@JiříHerník:严格意义上讲,它们不是正则表达式:Kleene并未将其定义为正则表达式。某些正则表达式引擎确实实现了一些额外的功能,使它们不仅解析正则语言,而且还解析更多内容。
–威廉·范·昂森(Willem Van Onsem)
19年6月10日在21:27
#7 楼
实际上,可以使用.NET正则表达式来完成此操作,但它并非易事,因此请仔细阅读。您可以在这里阅读一篇不错的文章。您可能还需要阅读.NET正则表达式。您可以在这里开始阅读。
使用了方括号
<>
,因为它们不需要转义。正则表达式如下:
<
[^<>]*
(
(
(?<Open><)
[^<>]*
)+
(
(?<Close-Open>>)
[^<>]*
)+
)*
(?(Open)(?!))
>
#8 楼
这是权威的正则表达式:\(
(?<arguments>
(
([^\(\)']*) |
(\([^\(\)']*\)) |
'(.*?)'
)*
)
\)
示例:
input: ( arg1, arg2, arg3, (arg4), '(pip' )
output: arg1, arg2, arg3, (arg4), '(pip'
请注意,
'(pip'
的正确管理字符串。(在调节器中尝试:http://sourceforge.net/projects/regulator/)
评论
如果没有嵌套,或者您只关心最里面的组,那么我喜欢这种技术。它不依赖于递归。我能够使用它来提取包含括号的参数。我在Regex101上做了一个工作示例
–本
10月17日在3:24
#9 楼
除气泡泡泡的答案外,还有其他支持递归构造的正则表达式版本。Lua
使用
%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)」)
请参见演示。正则表达式解决方案
有关如何在平衡括号之间获取表达式的信息,请参阅p的答案。
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żew
17年11月8日在13:11
#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 楼
使用Ruby(1.9.3或更高版本)的正则表达式:/(?<match>\((?:\g<match>|[^()]++)*\))/
在rubular上的演示
#12 楼
您需要第一个和最后一个括号。使用类似这样的东西:str.indexOf('(');-它会给您首次出现
str.lastIndexOf(')'); -最后一个
,因此您需要一个字符串,
String searchedString = str.substring(str1.indexOf('('),str1.lastIndexOf(')');
#13 楼
答案取决于您是否需要匹配匹配的括号集合,还是仅需要匹配输入文本中的第一个开到最后一个闭括号。如果您需要匹配匹配的嵌套括号,那么您还需要更多比正则表达式-请参见@dehmann
如果它是第一次打开到最后关闭,请参见@Zach
决定要发生的事情:
abc ( 123 ( foobar ) def ) xyz ) ghij
在这种情况下,您需要确定代码需要匹配的内容。
评论
这不是答案。
–艾伦·摩尔
2015年11月23日下午5:45
是的,对问题进行更改的要求应作为评论,
–绞肉
2015年12月16日上午10:32
#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 楼
在这种情况下,我也陷入了嵌套模式的困境。正则表达式是解决上述问题的正确方法。使用以下图案
'/(\((?>[^()]+|(?1))*\))/'
#16 楼
因为js regex不支持递归匹配,所以我无法使平衡括号匹配有效。所以这是一个简单的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>=0} is not regular
形式的语言。正则表达式只能匹配构成常规语言集的内容。在此处了解更多信息
#18 楼
我不使用正则表达式,因为它很难处理嵌套代码。因此,此代码段应能够使您使用带有括号的方括号来捕获代码段: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
我用它来从文本文件中提取代码段。
#19 楼
这可能有助于匹配括号。\s*\w+[(][^+]*[)]\s*
#20 楼
这不能完全解决OP问题,但我对一些来这里搜索嵌套结构regexp的人可能有用:从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;
};
#21 楼
这也起作用了re.findall(r'\(.+\)', s)
评论
这个问题很糟糕,因为不清楚它在问什么。所有答案对它的解释都不同。 @DaveF能否请您澄清问题?在这篇文章中回答:stackoverflow.com/questions/6331065/…