如何使用正则表达式删除括号内的文本?

109

我正在处理一堆文件,需要修改它们以删除文件名中的无关信息。尤其是我想要删除括号内的文本。例如:

filename = "Example_file_(extra_descriptor).ext"

我想对一大堆文件进行正则表达式匹配,其中括号表达式可能在中间或结尾,并且长度不固定。

这个正则表达式应该是什么样的呢?最好使用Perl或Python语法。


你确定 "extra_descriptor" 不能包含 ")" 吗?如果可以,问题就变得更加困难了... - dmckee --- ex-moderator kitten
2
@dmckee:如果括号可以嵌套,那就更难了,但是如果您只想去掉第一个'('和最后一个')'之间的所有内容,那就不太难了:只需使用贪婪的'.'而不是'.?'即可。 - j_random_hacker
2
@j_random_hacker 你说得对,由于嵌套的括号不能被有限状态机(FSM)识别(必须要跟踪无限的嵌套级别),所以它要难得多,并且也无法通过正则表达式来实现。如果想做到这一点,就必须将自己限制在有限的嵌套级别内。 - skyking
9个回答

174
s/\([^)]*\)//

那么在 Python 中,你需要这样做:

re.sub(r'\([^)]*\)', '', filename)

2
有没有理由更喜欢使用 .*? 而不是 [^)]*? - Kip
@Kip:不对。我不知道为什么,但.* 总是首先想到的东西。 - Can Berk Güder
20
.* 获取第一个左括号和最后一个右括号之间的所有内容:'a(b)c(d)e' 将变为 'ae'。 [^)]* 仅删除第一个左括号和第一个右括号之间的内容:'ac(d)e'。嵌套括号会有不同的行为。 - daotoad
@ovgolovin:好的。顺便说一下,在一般情况下正则表达式不等同,除非使用re.DOTALL标志。此外,你尝试过在Python中测量吗? - jfs
我想在Visual Studio中完成这个任务。Visual Studio的正则表达式是\([^)]*\) - DiB
显示剩余8条评论

130

匹配在括号中 没有其他的()字符存在的子字符串(例如(xyz 123)Text (abc(xyz 123)中)的模式是

\([^()]*\)

详细信息:

  • \( - 左圆括号(在 POSIX BRE 中应使用 (,请参见下面的 sed 示例)
  • [^()]* - 零个或多个(由于 * Kleene star quantifier)字符, 不包含否定字符类/POSIX 括号表达式 中定义的那些字符,即除了()之外的任何字符
  • \) - 右圆括号(在 POSIX BRE 中不允许转义)

删除代码片段:

  • JavaScript: string.replace(/\([^()]*\)/g, '')
  • PHP: preg_replace('~\([^()]*\)~', '', $string)
  • Perl: $s =~ s/\([^()]*\)//g
  • Python: re.sub(r'\([^()]*\)', '', s)
  • C#: Regex.Replace(str, @"\([^()]*\)", string.Empty)
  • VB.NET: Regex.Replace(str, "\([^()]*\)", "")
  • Java: s.replaceAll("\\([^()]*\\)", "")
  • Ruby: s.gsub(/\([^()]*\)/, '')
  • R: gsub("\\([^()]*\\)", "", x)
  • Lua: string.gsub(s, "%([^()]*%)", "")
  • Bash/sed: sed 's/([^()]*)//g'
  • Tcl: regsub -all {\([^()]*\)} $s "" result
  • C++ std::regex: std::regex_replace(s, std::regex(R"(\([^()]*\))"), "")
  • Objective-C:
    NSRegularExpression *regex = [NSRegularExpression regularExpressionWithPattern:@"\\([^()]*\\)" options:NSRegularExpressionCaseInsensitive error:&error]; NSString *modifiedString = [regex stringByReplacingMatchesInString:string options:0 range:NSMakeRange(0, [string length]) withTemplate:@""];
  • Swift: s.replacingOccurrences(of: "\\([^()]*\\)", with: "", options: [.regularExpression])
  • Google BigQuery: REGEXP_REPLACE(col, "\\([^()]*\\)" , "")

亲爱的Wiktor,我只有一个问题。如果我们要排除方括号[而不是圆括号,那么在[^ ]结构中是否需要转义它们,例如[^\\[\\]],或者像其他字符一样不需要转义? - Anoushiravan R
1
这取决于正则表达式的风格。请参见我的这个回答 - Wiktor Stribiżew

24

我会使用:

\([^)]*\)

这个答案对于像这样带有嵌套括号的文件名 "filename_abc(text(TM))" 将无法工作,因为结果将是 "filename_abc)"。 - The Human Cat

7
如果您没有绝对必要使用正则表达式,考虑使用Perl的Text::Balanced来移除括号。
use Text::Balanced qw(extract_bracketed);

my ($extracted, $remainder, $prefix) = extract_bracketed( $filename, '()', '[^(]*' );

{   no warnings 'uninitialized';

    $filename = (defined $prefix or defined $remainder)
                ? $prefix . $remainder
                : $extracted;
}

你可能会想,“为什么要这样做,使用正则表达式只需要一行代码就能搞定呢?”
$filename =~ s/\([^}]*\)//;

Text::Balanced可以处理嵌套的括号。因此,$filename = 'foo_(bar(baz)buz)).foo'将被正确提取。在此处提供的基于正则表达式的解决方案将无法处理此字符串。其中一个会停在第一个闭合括号处,而另一个则会将所有括号都吃掉。

   $filename =~ s/\([^}]*\)//;
   # returns 'foo_buz)).foo'

   $filename =~ s/\(.*\)//;
   # returns 'foo_.foo'

   # text balanced example returns 'foo_).foo'

如果两种正则表达式的行为都可接受,请使用正则表达式--但要记录限制和所做的假设。


虽然我知道你不能用(经典)正则表达式解析嵌套的括号,但是如果你知道你永远不会遇到嵌套的括号,你可以简化问题,使它能够使用正则表达式来处理,并且相当容易。在我们不需要时使用解析器工具就有些过度了。 - Chris Lutz
@Chris Lutz - 我应该在第一句话中说“考虑”,而不是“使用”。在许多情况下,正则表达式可以完成工作,这就是为什么我建议如果行为可接受,可以考虑使用正则表达式的原因。 - daotoad

3

对于想要使用Python的人,这里有一个简单的例程,可以删除带有括号的子字符串,包括嵌套括号的子字符串。虽然它不是正则表达式,但它可以完成工作!

def remove_nested_parens(input_str):
    """Returns a copy of 'input_str' with any parenthesized text removed. Nested parentheses are handled."""
    result = ''
    paren_level = 0
    for ch in input_str:
        if ch == '(':
            paren_level += 1
        elif (ch == ')') and paren_level:
            paren_level -= 1
        elif not paren_level:
            result += ch
    return result

remove_nested_parens('example_(extra(qualifier)_text)_test(more_parens).ext')

3

如果路径中可能包含括号,则r'\(.*?\)'正则表达式不足以匹配:

import os, re

def remove_parenthesized_chunks(path, safeext=True, safedir=True):
    dirpath, basename = os.path.split(path) if safedir else ('', path)
    name, ext = os.path.splitext(basename) if safeext else (basename, '')
    name = re.sub(r'\(.*?\)', '', name)
    return os.path.join(dirpath, name+ext)

默认情况下,该函数保留路径的目录和扩展名部分中的带括号的块。

例如:

>>> f = remove_parenthesized_chunks
>>> f("Example_file_(extra_descriptor).ext")
'Example_file_.ext'
>>> path = r"c:\dir_(important)\example(extra).ext(untouchable)"
>>> f(path)
'c:\\dir_(important)\\example.ext(untouchable)'
>>> f(path, safeext=False)
'c:\\dir_(important)\\example.ext'
>>> f(path, safedir=False)
'c:\\dir_\\example.ext(untouchable)'
>>> f(path, False, False)
'c:\\dir_\\example.ext'
>>> f(r"c:\(extra)\example(extra).ext", safedir=False)
'c:\\\\example.ext'

1
如果您可以使用sed(可能在程序内部执行),那么它将非常简单:
sed 's/(.*)//g'

你只是在对表达式 .* 进行分组。 - Gumbo
@Gumbo:不,他不是。在sed中,“\(...\)”表示分组。 - runrig

0
>>> import re
>>> filename = "Example_file_(extra_descriptor).ext"
>>> p = re.compile(r'\([^)]*\)')
>>> re.sub(p, '', filename)
'Example_file_.ext'

0

Java 代码:

Pattern pattern1 = Pattern.compile("(\\_\\(.*?\\))");
System.out.println(fileName.replace(matcher1.group(1), ""));

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接