我需要一个正则表达式来选择两个外括号之间的所有文本。
示例:
START_TEXT(text here(possible text)text(possible text(more text)))END_TXT
^ ^
结果:
(text here(possible text)text(possible text(more text)))
我想添加此答案以供快速参考。随时更新。
.NET 正则表达式 使用 balancing groups。
\((?>\((?<c>)|[^()]+|\)(?<-c>))*(?(c)(?!))\)
其中 c
用作深度计数器。
Stack Overflow:使用 RegEx 平衡匹配括号
Wes 令人费解的博客:使用 .NET 正则表达式匹配平衡结构
Greg Reinacker 的博客:正则表达式中的嵌套构造
PCRE 使用 recursive pattern。
\((?:[^)(]+|(?R))*+\)
Demo at regex101;或没有交替:
\((?:[^)(]*(?R)?)*+\)
Demo at regex101;或 unrolled 以获得性能:
\([^)(]*+(?:(?R)[^)(]*)*+\)
Demo at regex101;该模式粘贴在代表 (?0)
的 (?R)
处。
Perl、PHP、Notepad++、 R:perl=TRUE、Python:Regex package 和 (?V1)
用于 Perl 行为。
Ruby 使用 subexpression calls。
在 Ruby 2.0 中,\g<0>
可用于调用完整模式。
\((?>[^)(]+|\g<0>)*\)
Demo at Rubular; Ruby 1.9 仅支持 capturing group recursion:
(\((?>[^)(]+|\g<1>)*\))
Demo at Rubular (从 Ruby 1.9.3 开始的atomic grouping)
JavaScript API :: XRegExp.matchRecursive
XRegExp.matchRecursive(str, '\\(', '\\)', 'g');
JS、Java 和其他没有递归的正则表达式风格,最多 2 级嵌套:
\((?:[^)(]+|\((?:[^)(]+|\([^)(]*\))*\))*\)
Demo at regex101。更深入的 nesting needs to be added 模式。
在不平衡括号 drop the +
quantifier. 上更快地失败
Java:一个有趣的idea using forward references by @jaytea。
Reference - What does this regex mean?
rexegg.com - 递归正则表达式
Regular-Expressions.info - 正则表达式递归
正则表达式对于这项工作来说是错误的工具,因为您正在处理嵌套结构,即递归。
但是有一个简单的算法可以做到这一点,我在 in this answer 到 previous question 中进行了更详细的描述。要点是编写代码来扫描字符串,保持一个开括号的计数器,这些开括号还没有被右括号匹配。当该计数器归零时,您就知道您已到达最后的右括号。
您可以使用 regex recursion:
\(([^()]|(?R))*\)
Unrecognized grouping construct
。
[^\(]*(\(.*\))[^\)]*
[^\(]*
匹配字符串开头不是左括号的所有内容,(\(.*\))
捕获括在括号中的所需子字符串,[^\)]*
匹配字符串末尾不是右括号的所有内容。请注意,此表达式不会尝试匹配括号;一个简单的解析器(见 dehmann's answer)会更适合。
(?<=\().*(?=\))
如果您想在两个匹配的括号之间选择文本,那么您对正则表达式不走运。这是不可能的(*)。
此正则表达式仅返回字符串中第一个左括号和最后一个右括号之间的文本。
(*) 除非您的正则表达式引擎具有 balancing groups or recursion 等功能。支持此类功能的引擎数量正在缓慢增长,但它们仍然不是普遍可用的。
这个答案解释了为什么正则表达式不是这个任务的正确工具的理论限制。
正则表达式不能做到这一点。
正则表达式基于称为 Finite State Automata (FSA)
的计算模型。顾名思义,FSA
只能记住当前状态,它没有关于先前状态的信息。
https://i.stack.imgur.com/XqtHd.png
在上图中,S1 和 S2 是两个状态,其中 S1 是开始和最后一步。因此,如果我们尝试使用字符串 0110
,则转换如下:
0 1 1 0
-> S1 -> S2 -> S2 -> S2 ->S1
在上述步骤中,当我们在第二个S2
,即在解析 0110
的 01
之后,FSA 没有关于 01
中前一个 0
的信息,因为它只能记住当前状态和下一个状态输入符号。
在上面的问题中,我们需要知道左括号的编号;这意味着它必须存储在某个地方。但是由于 FSAs
不能这样做,所以不能编写正则表达式。
但是,可以编写一个算法来完成这项任务。算法通常属于 Pushdown Automata (PDA)
。 PDA
比 FSA
高一级。 PDA 有一个额外的堆栈来存储一些额外的信息。 PDA 可以用来解决上述问题,因为我们可以'push
' 堆栈中的左括号,并在遇到右括号时使用'pop
'。如果最后堆栈为空,则左括号和右括号匹配。否则不行。
我也陷入了嵌套模式出现的这种情况。
正则表达式是解决上述问题的正确方法。使用下面的模式
'/(\((?>[^()]+|(?1))*\))/'
这是最终的正则表达式:
\(
(?<arguments>
(
([^\(\)']*) |
(\([^\(\)']*\)) |
'(.*?)'
)*
)
\)
例子:
input: ( arg1, arg2, arg3, (arg4), '(pip' )
output: arg1, arg2, arg3, (arg4), '(pip'
请注意,'(pip'
已正确管理为字符串。 (在调节器中试过:http://sourceforge.net/projects/regulator/)
除了 bobble bubble's answer,还有其他支持递归构造的正则表达式风格。
卢阿
使用 %b()
(%b{}
/ %b[]
用于大括号/方括号):
for s in string.gmatch("Extract (a(b)c) and ((d)f(g))", "%b()") do print(s) end (见demo)
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)」)
请参阅demo。
Python re
非正则表达式解决方案
How to get an expression between balanced parentheses 参见 poke's answer。
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"
您需要第一个和最后一个括号。使用这样的东西:
str.indexOf('('); - 它会给你第一次出现
str.lastIndexOf(')'); - 最后一个
所以你需要一个字符串,
String searchedString = str.substring(str1.indexOf('('),str1.lastIndexOf(')');
因为 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
形式的语言。正则表达式只能匹配构成常规语言集的一部分的事物。
阅读更多@here
我没有使用正则表达式,因为它很难处理嵌套代码。所以这个片段应该能够让你抓住带有平衡括号的代码部分:
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
我用它从文本文件中提取代码片段。
这并没有完全解决 OP 问题,但我认为它可能对一些来这里搜索嵌套结构正则表达式的人有用:
从javascript中的函数字符串(具有嵌套结构)解析参数
https://i.stack.imgur.com/fiUlC.png
匹配括号、方括号、圆括号、单引号和双引号
Here you can see generated regexp in action
/**
* 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;
};
这可能有助于匹配平衡括号。
\s*\w+[(][^+]*[)]\s*
这个也有效
re.findall(r'\(.+\)', s)
(?>[^)(]+|(?R))*+
与写(?:[^)(]+|(?R))*+
是一样的。下一个模式也是如此。关于展开的版本,您可以在此处放置一个所有格量词:[^)(]*+
以防止回溯(如果没有右括号)。\{(?>\{(?<c>)|[^{}]+|\}(?<-c>))*(?(c)(?!))\}
(\((?:[^)(]+|(?1))*+\))
(或?2
、?3
等,具体取决于它是哪个数字组)而不是\((?:[^)(]+|(?R))*+\)
。?R
总是递归回到表达式的开头。如果您单独使用它,那很好。但是例如,如果您在if
语句之后发现逻辑比较,则if \((?:[^)(]+|(?R))*+\)
将不会匹配任何内容,因为if
也必须重复才能匹配,而不仅仅是括号。但是,if (\((?:[^)(]+|(?1))*+\))
只会检查一次if
,然后递归检查第一组。