如何在正则表达式中否定特定单词?

837

我知道可以使用[^bar]来否定一组字符,但我需要一个正则表达式,其中否定适用于具体的单词 - 所以在我的例子中,如何否定一个真正的bar,而不是“bar”中的任何字符?


12个回答

1003

一个很好的方法是使用负向先行断言

^(?!.*bar).*$
负向先行断言是一对括号,开头的括号后面跟着一个问号和一个感叹号。括号内部可以是任何正则表达式模式。

24
这就是全部(我可能会从(?!bar)开始并逐步构建)。我不明白为什么其他人要把它搞得这么复杂。 - Beta
6
行首字符在文章开头的位置做得相当不错。 - dhblah
3
干得好 - 匹配具有指定字符串且该字符串没有任何前导字符并且在其后面跟着任何字符的行。根据定义,这就是字符串的缺失!因为如果存在,它总会被某些东西预先出现,即使是行锚点^。 - Pete_ch
3
@NeilTraft 你觉得用 grep -v bar 怎么样 :) - bobbel
2
不幸的是,这对实际单词无效。 foo 将匹配,bar 不会,但 foobarbarfoo 也不会! - bzim
显示剩余13条评论

73

除非性能至关重要,否则通常更容易只运行第二遍结果,跳过与您想要否定的单词匹配的结果。

正则表达式通常意味着您正在执行脚本或某种低性能任务,因此找到一种易于阅读、易于理解和易于维护的解决方案。


41
有许多情况下你无法控制工作流程:你只需编写一个正则表达式来进行过滤。 - Steve Bennett
1
如果您想替换所有不符合特定正则表达式的文本,该怎么办? - user unknown
这个想法很特别,但它确实有效。大多数答案都是针对PCRE,但是它们的解决方案无法应用于re2 - Carson

69

解决方案:

^(?!.*STRING1|.*STRING2|.*STRING3).*$

xxxxxx好的

xxxSTRING1xxx不行(是否需要)

xxxSTRING2xxx不行(是否需要)

xxxSTRING3xxx不行(是否需要)


7
谢谢,这为我提供了多个单词所需的额外信息。 - RozzA
6
我是唯一一个讨厌“OK”和“KO”作为测试通过指标的人吗?这只有一个打字错误就会导致灾难... - AJPerez
@AJPerez,是的,“OK”和“KO”是测试的结果。 - undefined

57

你可以使用负向前瞻或后顾来实现:

^(?!.*?bar).*
^(.(?<!bar))*?$

或者只使用基础知识:

^(?:[^b]+|b(?:$|[^a]|a(?:$|[^r])))*$

这些都匹配不包含 bar 的任何内容。


哪些编程语言不支持正则表达式中的(负)后顾和/或(负)前瞻? - JAB
8
我会尽力做到最好。这句话的意思是,从你的模式来看,完全不清楚你所做的只是拒绝单词“bar”。 - Bryan Oakley
@Bryan:实际上,它并没有拒绝单词“bar”。它只是在“ar”后面跟着“b”时拒绝了它。 - JAB
好主意,但不是所有地方都支持。据我所知,JavaScript 支持负向前瞻,但不支持向后查找。我不了解其他语言的细节,但这可能会有所帮助:https://en.wikipedia.org/wiki/Comparison_of_regular_expression_engines - mik01aj
@JAB,Bash不支持负回溯/正回溯。 - niieani
显示剩余3条评论

43
以下正则表达式可以实现你想要的功能(只要支持负向回顾和正向回顾),正确匹配事物;唯一的问题在于它匹配单个字符(即每次匹配都是一个单独的字符,而不是两个连续的“bar”之间的所有字符),如果你正在处理非常长的字符串可能会导致潜在的高开销。
b(?!ar)|(?<!b)a|a(?!r)|(?<!ba)r|[^bar]

7
为了避免我们在查看最终答案前被迫阅读错误答案,为什么不重写你的答案并使其完整,但不包含有点混淆的不良部分呢?如果有人真的关心编辑历史记录,他们可以使用本网站内置的功能。 - Bryan Oakley
17
两年半前我写下这个答案,但没问题,我可以为你翻译。 - JAB
3
该死,那好疼。试试这个吧(?:(?!bar).)*。 - Bob
@Mary,这个不会按预期工作。例如,在 foobar 上使用 /(?:(?!bar).)*/g 可以返回 fooar - Krzysiek

36

我阅读了这个论坛帖子,试图找到以下英文语句的正则表达式:

给定一个输入字符串,匹配所有内容,除非该输入字符串恰好为“bar”;例如,我想匹配“barrier”和“disbar”,以及“foo”。

这是我想出来的正则表达式

^(bar.+|(?!bar).*)$

我的翻译是:如果字符串以“bar”开头并且至少有一个其他字符,则匹配该字符串,或者如果字符串不以“bar”开头。


@ReReqest - 如果您将此问题发布为单独的问题,就会有更好的机会得到答案。如果需要的话,您可以提供指向此问题的链接。关于问题的实质,它看起来还不错,但我不是正则表达式大师。 - Bostone
2
那就是我在寻找的那一个。它真的与除了bar以外的所有东西都很匹配。 - Gabriel Hautclocq
4
^(?!bar$).*与此相同(除了完全匹配 bar 之外的所有内容),并避免了重复。 - bkDJ

23

被接受的答案虽然很好,但实际上是在正则表达式中缺少简单的子表达式非运算符的情况下的一种解决方法。这就是为什么 grep --invert-match 存在的原因。所以在 *nixes 中,您可以使用管道和第二个正则表达式来实现所需的结果。

grep 'something I want' | grep --invert-match 'but not these ones'

依然是一种变通方法,但可能更容易记住。


1
这是对于使用grep的人来说正确的答案,它肯定符合正则表达式。我只希望这个答案更加突出(甚至包括在被接受的答案中),这样我就不必先花时间看其他答案了。 - user2225804
我在R中找不到“反向匹配”的选项。它只限于Unix grep吗? - Lazarus Thurston
我使用基于GUI的grep工具,例如TextCrawler。但如果您不使用Windows操作系统,我不确定该使用什么工具。 - Jon Grah

11

此评论中提取,由bkDJ

^(?!bar$).*

这个解决方案的好处是可以清楚地否定(排除)多个单词。
^(?!bar$|foo$|banana$).*

9
为什么需要在正则表达式中加上末尾的 .* - Sasha Bond
1
因为负向先行断言没有匹配到任何字符。 - B2K
似乎通过提取 $ 来工作:^(?!(bar|foo|banana)$).* :-) - Glenn Mohammad

8
如果您真的不想匹配的是一个单词 "word" 以及 "bar",那么请按照以下方式操作:
^(?!.*\bbar\b).*$

上述模式将匹配不包含 bar 的任何字符串,该字符串位于单词边界上,也就是说与非单词字符分离。但是,上述模式中使用的句点 / 点(.)除非使用正确的正则表达式标志,否则不会匹配换行符。
^(?s)(?!.*\bbar\b).*$

或者:

^(?!.*\bbar\b)[\s\S]*$

我们不使用任何特殊标志,而是寻找任何空格或非空格字符。这应该覆盖了每一个字符。

但是,如果我们想匹配可能包含bar的单词,但只是不包含特定单词bar怎么办?

(?!\bbar\b)\b\[A-Za-z-]*bar[a-z-]*\b
  1. (?!\bbar\b)断言下一个输入不是单词边界上的bar
  2. \b\[A-Za-z-]*bar[a-z-]*\b匹配包含bar的任何单词,单词必须在单词边界上。

查看正则表达式演示


4
我希望能够补充已接受的答案,并通过我的晚回答为讨论做出贡献。
@ChrisVanOpstal分享了这个正则表达式教程this regex tutorial,这是一个学习正则表达式的好资源。
然而,它阅读起来真的很耗时。
我制作了一个助记符方便的速查表。
这个参考基于括号[](){}引导的每个类,我发现这很容易记忆。
Regex = {
 'single_character': ['[]', '.', {'negate':'^'}],
 'capturing_group' : ['()', '|', '\\', 'backreferences and named group'],
 'repetition'      : ['{}', '*', '+', '?', 'greedy v.s. lazy'],
 'anchor'          : ['^', '\b', '$'],
 'non_printable'   : ['\n', '\t', '\r', '\f', '\v'],
 'shorthand'       : ['\d', '\w', '\s'],
 }

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