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

How to negate specific word in regex?

我知道我可以否定一组字符,就像在[^bar]中一样,但我需要一个正则表达式,其中否定适用于特定的单词——所以在我的例子中,我如何否定实际的"bar"而不是"any chars in bar"


实现这一点的一个好方法是使用负面展望:

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

The negative lookahead construct is the pair of parentheses, with the opening parenthesis followed by a question mark and an exclamation point. Inside the lookahead [is any regex pattern].


除非性能是最令人担忧的,否则通常只需通过第二次测试就可以轻松地运行结果,跳过那些与您想要否定的词匹配的测试。

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


下面的regex将做您想要做的(只要支持负lookbehinds和lookaheads),正确匹配事物;唯一的问题是它匹配单个字符(即每个匹配是单个字符,而不是两个连续的"bar"之间的所有字符),如果您E使用很长的弦。

1
b(?!ar)|(?<!b)a|a(?!r)|(?<!ba)r|[^bar]


你可以用消极的眼光看前方或者看后面:

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

或者只使用基础知识:

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

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


我在为下面的英语声明识别regex时遇到了这个论坛主题:

Given an input string, match everything unless this input string is exactly 'bar'; for example I want to match 'barrier' and 'disbar' as well as 'foo'.

这是我想出的雷吉士

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

我对regex的英文翻译是"匹配字符串,如果它以"bar"开头并且至少有一个其他字符,或者如果字符串不是以"bar"开头。


解决方案:

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

XXXXXX OK

XXXString1XXX KO(是否需要)

XXXString2XXX KO(是否需要)

XXXString3xx ko(是否需要)


接受的答案很好,但实际上是为了解决正则表达式中缺少简单的子表达式否定运算符的问题。这就是grep --invert-match退出的原因。所以在*nixes中,您可以使用管道和第二个regex来完成所需的结果。

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

仍然是一个解决方案,但可能更容易记住。


我希望补充已接受的答案,并以我迟交的答案参与讨论。

@Chrisvanopstal分享了这个regex教程,这是学习regex的一个很好的资源。

不过,看完这本书真的很费时。

为了便于记忆,我做了一张纸条。

这个参考是基于每个类的括号[](){},我发现很容易回忆起来。

1
2
3
4
5
6
7
8
9
10
Regex = {
 'single_character': ['[]', '.', {'negate':'^'}],
 'capturing_group' : ['()', '|', '\', 'backreferences and named group'],
 'repetition'      : ['{}', '*', '+', '?', 'greedy v.s. lazy'],
 'anchor'          : ['^', '\b', '$'],
 'non_printable'   : ['
', '\t', '
', '\f', '\v'],
 'shorthand'       : ['\d', '\w', '\s'],
 }

我有一个文件名列表,我想用这种行为(ruby)排除某些文件名:

1
2
3
4
5
6
7
8
9
10
11
12
files = [
  'mydir/states.rb',      # don't match these
  'countries.rb',
  'mydir/states_bkp.rb',  # match these
  'mydir/city_states.rb'
]
excluded = ['states', 'countries']

# set my_rgx here

result = WankyAPI.filter(files, my_rgx)  # I didn't write WankyAPI...
assert result == ['mydir/city_states.rb', 'mydir/states_bkp.rb']

我的解决方案是:

1
2
excluded_rgx = excluded.map{|e| e+'\.'}.join('|')
my_rgx = /(^|\/)((?!#{excluded_rgx})[^\.\/]*)\.rb$/

我对此应用程序的假设:

  • 要排除的字符串位于输入的开头,或紧跟在斜线后面。
  • 允许的字符串以.rb结尾。
  • 允许的文件名在.rb之前没有.字符。

想一想其他可以做的事。它和我的第一个答案非常不同,因为它不使用正则表达式,所以我决定写第二个答案。

在字符串上使用您选择的语言的split()方法等价物,并使用要否定的单词作为拆分内容的参数。使用python的示例:

1
2
3
>>> text = 'barbarasdbarbar 1234egb ar bar32 sdfbaraadf'
>>> text.split('bar')
['', '', 'asd', '', ' 1234egb ar ', '32 sdf', 'aadf']

至少在Python中这样做是很好的(我不记得在Visual Basic或Java中的功能是否是相同的),它可以让你间接知道"bar"在字符串中重复的原因,因为"bar"之间的空字符串被包含在结果列表中(尽管开始时是空字符串)。宁是由于在字符串的开头有一个"条")。如果不想这样做,只需从列表中删除空字符串即可。


摘自BKDJ的评论:

1
^(?!bar$).*

此解决方案的优点是可以清楚地否定(排除)多个单词:

1
^(?!bar$|foo$|banana$).*