我需要一个正则表达式来选择两个外括号之间的所有文本。
例: some text(text here(possible text)text(possible text(more text)))end text
结果: (text here(possible text)text(possible text(more text)))
我需要一个正则表达式来选择两个外括号之间的所有文本。
例: some text(text here(possible text)text(possible text(more text)))end text
结果: (text here(possible text)text(possible text(more text)))
Answers:
我想添加此答案作为快速参考。随时更新。
.NET正则表达式使用平衡组。
\((?>\((?<c>)|[^()]+|\)(?<-c>))*(?(c)(?!))\)
哪里 c
用作深度计数器。
PCRE使用递归模式。
\((?:[^)(]+|(?R))*+\)
regex101上的演示;或无交替:
\((?:[^)(]*(?R)?)*+\)
regex101上的演示;或为提高性能而展开:
\([^)(]*+(?:(?R)[^)(]*)*+\)
regex101上的演示;图案粘贴在(?R)
代表(?0)
。
Perl,PHP,Notepad ++, R:perl = TRUE,Python:带有用于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的正向引用的有趣想法。
(?>[^)(]+|(?R))*+
是一样的(?:[^)(]+|(?R))*+
。下一个模式也一样。关于展开的版本,您可以在此处放置一个所有格量词:[^)(]*+
防止回溯(以防万一)。
(...(..)..(..)..(..)..(..)..)
)目标字符串中),你可以使用一个简单的非捕获组和封闭所有的原子团:(?>(?:[^)(]+|\g<1>)*)
(这就像所有格量词一样)。在Ruby 2.x中,所有格量词可用。
您可以使用正则表达式递归:
\(([^()]|(?R))*\)
Unrecognized grouping construct
。
[^\(]*(\(.*\))[^\)]*
[^\(]*
匹配字符串开头的不是(\(.*\))
左括号的[^\)]*
所有内容,捕获括号中所需的子字符串,并匹配字符串末尾的不是右括号的所有内容。请注意,此表达式不尝试与方括号匹配;一个简单的解析器(请参阅dehmann的答案)将更适合于此。
(?<=\().*(?=\))
如果要在两个匹配的括号之间选择文本,则不适合使用正则表达式。这是不可能的(*)。
此正则表达式只返回字符串中第一个开括号和最后一个闭括号之间的文本。
(*)除非您的正则表达式引擎具有平衡组或递归之类的功能。支持此类功能的引擎数量正在缓慢增长,但是仍然不是很普遍。
这个答案解释了为什么正则表达式不是执行此任务的正确工具的理论限制。
正则表达式不能做到这一点。
正则表达式基于称为的计算模型Finite State Automata (FSA)
。顾名思义,a FSA
只能记住当前状态,而没有关于先前状态的信息。
在上图中,S1和S2是两个状态,其中S1是开始步骤和最终步骤。因此,如果我们尝试使用字符串0110
,则转换如下:
0 1 1 0
-> S1 -> S2 -> S2 -> S2 ->S1
在上面的步骤中,当我们处于第二个位置时,S2
即在解析01
之后0110
,FSA没有关于前一个输入的信息0
,01
因为它只能记住当前状态和下一个输入符号。
在上述问题中,我们需要知道开括号的编号;这意味着它必须存储在某个地方。但是由于FSAs
不能做到这一点,所以不能编写正则表达式。
但是,可以编写算法来完成此任务。算法通常属于Pushdown Automata (PDA)
。PDA
是的上一级FSA
。PDA有一个额外的堆栈来存储一些其他信息。PDA可用于解决上述问题,因为我们可以push
在堆栈中“打开括号pop
”,并在遇到闭合括号后“”。如果最后堆栈是空的,则打开括号和关闭括号匹配。否则不行。
这是权威的正则表达式:
\(
(?<arguments>
(
([^\(\)']*) |
(\([^\(\)']*\)) |
'(.*?)'
)*
)
\)
例:
input: ( arg1, arg2, arg3, (arg4), '(pip' )
output: arg1, arg2, arg3, (arg4), '(pip'
请注意,将'(pip'
正确地管理为字符串。(已在监管机构中试用:http : //sourceforge.net/projects/regulator/)
除了气泡的答案外,还有其他支持递归构造的正则表达式。
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)))]
"""
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"
答案取决于您是否需要匹配匹配的方括号组,还是仅匹配输入文本中的第一个开盘到最后一个开盘。
如果需要匹配匹配的嵌套方括号,则除了正则表达式外,还需要其他内容。-见@dehmann
如果它只是第一次打开到最后关闭,请参阅@Zach
决定要发生的事情:
abc ( 123 ( foobar ) def ) xyz ) ghij
在这种情况下,您需要确定代码需要匹配的内容。
因为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' } ]
尽管有许多答案以某种形式提到正则表达式不支持递归匹配等,但这样做的主要原因是计算理论的根源。
表格语言 {a^nb^n | n>=0} is not regular
。正则表达式只能匹配构成常规语言集的内容。
在这里阅读更多
我不使用正则表达式,因为它很难处理嵌套代码。因此,此代码段应能够使您使用括号括起来的代码段:
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
我用它来从文本文件中提取代码片段。
这对某些人可能有用:
/**
* 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可能有用。