正则表达式和否定整个字符组

278

我正在尝试一些对我来说应该很显然但却不是的事情。 我想匹配一个不包含特定字符序列的字符串。 我已经尝试使用 [^ab], [^(ab)] 等来匹配不包含'a'或'b'的字符串,或只包含 'a' 或只包含 'b' 或者 'ba' 但不匹配 'ab'。 我给出的这些示例确实不会匹配 'ab',但也不会单独匹配 'a',而我需要它们这样做。 有没有简单的方法可以做到这一点?


@finnw也许他是在参考https://dev59.com/7Zbfa4cB1Zd3GeqPrUiW的上下文中提到它的? - user3186555
9个回答

445

使用字符类(如[^ab])可以匹配不在字符集中的单个字符。(其中^是否定部分)。

要匹配不包含多字符序列ab的字符串,您需要使用负向先行断言:

^(?:(?!ab).)+$


以上表达式在正则表达式注释模式下的解析是:

(?x)    # enable regex comment mode
^       # match start of line/string
(?:     # begin non-capturing group
  (?!   # begin negative lookahead
    ab  # literal text sequence ab
  )     # end negative lookahead
  .     # any single character
)       # end non-capturing group
+       # repeat previous match one or more times
$       # match end of line/string

48
分解这个正则表达式对我非常有帮助。谢谢。 - Gibado
1
而要进行替换,可能只需要使用 ^((?!ab).+)$ - phil294
1
一个小提示。在“任何单个字符”的正则表达式中,“.”只适用于同一行。如果您需要对多行正则表达式执行此操作,则可能需要将其替换为(.|\n) - Thiago Mata
谢谢你的分享,非常有启发性。在尝试过后,我认为值得注意的是,你所描述并且在示例中使用的“ab”实际上可以是一个复杂的正则表达式。因此,如果你有一个匹配字符串中某个模式的正则表达式,那么将该正则表达式包装在“^(?:(?!'和').)+$”中,结果的正则表达式将匹配不包含原始正则表达式匹配项的字符串。 - Nikkorian
RegexBuddy的调试功能非常出色,能很好地演示负向预查的工作原理。 - KalenGi

236
使用负向先行断言(参见Regexr.com explanation):
^(?!.*ab).*$

更新:在下面的评论中,我已经说明这种方法比Peter's answer提供的方法慢。自那时以来,我进行了一些测试,发现它确实稍微快一点。然而,选择这种技术而不是其他技术的原因不是速度,而是简单性。

另一种技术,在这里描述为温和贪婪的标记,适用于更复杂的问题,比如匹配由多个字符组成的分隔文本(如HTML,正如Luke在下面评论中所说)。对于问题的描述,这太过复杂。

对于任何感兴趣的人,我使用了一大段Lorem Ipsum文本进行测试,计算不包含单词“quo”的行数。以下是我使用的正则表达式:

(?m)^(?!.*\bquo\b).+$

(?m)^(?:(?!\bquo\b).)+$

无论我是在整个文本中搜索匹配项,还是将其分成行并逐个匹配,锚定的前瞻始终优于浮动的前瞻。

15
我认为这样会更有效率:(?:(?!ab).)*。(说明:该正则表达式的意思是匹配任何不包含子字符串 "ab" 的字符串。) - Blixt
8
@Blixit:是的,没错。但它对于正则表达式新手来说更难阅读。我发布的那个足以高效地应用在大多数情况下。 - Alan Moore
32
不要编写面向新手的代码!如果代码难以阅读,请添加注释/文档,以便他们可以学习,而不是使用更简单的代码让他们一知半解。 - Peter Boughton
36
如果我认为两种方法之间会有明显的差异,我就不会犹豫地推荐更快的那一个。另一方面,正则表达式非常晦涩(如果不是神秘的话),我认为将知识尽可能地拆分成更小、更易管理的部分是值得的。 - Alan Moore
1
在我的情况下,第二个方法起作用了,而第一个则没有。我试图匹配某些包含 windows<td> .. </td> 元素,这些元素在开始和结束标签之间出现,并且不匹配那些不包含的 TD 元素。我使用了 <td(?:(?!</td>).)+</td> 来查找整个 TD 元素,其中 <td(?!.*</td>).*</td> 无法工作。最终的正则表达式是 <td(?:(?!</td>).)+windows.*?</td>。关于“将知识分解成更小的块”的一个很好的例子,请参见下面的答案,其中包括所使用的正则表达式字符的解释。 - Luke
显示剩余10条评论

71

是的,它被称为负向前瞻。它的格式是 - (?! 正则表达式)。因此,abc(?!def) 将匹配不跟在 def 后面的 abc。所以它将匹配 abce、abc、abck 等。

类似地,有正向前瞻 - (?= 正则表达式)。因此,abc(?=def) 将匹配 abc 后面紧跟着的 def。

还有负向后顾 - (?<! 正则表达式) 和正向后顾 - (?<= 正则表达式)

需要注意的一点是,负向前瞻是零宽度的。也就是说,它不算占用任何空间。

所以看起来 a(?=b)c 会匹配 "abc",但实际上不会。它将匹配 'a',然后与'b'进行正向前瞻,但不会向前移动字符串。然后它将尝试使用 'b' 将 'c' 与之匹配,这是不起作用的。同样,^a(?=b)b$ 将匹配 'ab' 而不是 'abb',因为在大多数正则表达式实现中,前瞻是零宽度的。

更多信息请参考此页面


3
提到“回顾后发”操作符也很有用,因为并不是所有在线正则表达式解析器/文档都会包含它,即使它是有效的并且可行。 - Leith
在regex101.com中,?!可以忽略该组,但它仍然匹配字符串的其余部分。是否有一种方法可以使用它来排除整行,如果它具有特定模式? - Guilherme Taffarel Bergamin

6

abc(?!def)将匹配不跟在def后面的abc。所以它将匹配abce、abc、abck等。如果我既不想要def也不想要xyz,那么会是abc(?!(def)(xyz))吗?

我有同样的问题,并找到了解决方案:

abc(?:(?!def))(?:(?!xyz))

这些不可数的组合是由“AND”组合的,所以这应该可以解决问题。希望能帮到您。

那个引用是从哪里来的?只有一部分来自于这个答案。除此之外,你没有回答问题,但似乎回答了一些你没有链接到的东西。我认为 abc(?:(?!def)(?!xyz)) 就可以了。它们已经在非捕获组中了。不需要再把另一个放进去。它们也不是“由“AND”组合而成的”。它们一个接一个地被检查,就像 ab 首先被检查是否为 a,然后再检查是否为 b,但是前瞻不会移动光标。 - Scratte

5

按照您所描述的方式使用正则表达式是简单的方法(就我所知)。如果您想要一个范围,可以使用 [^a-f]。


4

最简单的方法是完全将否定从正则表达式中提取出来:

if (!userName.matches("^([Ss]ys)?admin$")) { ... }

虽然如果你只是使用这个表达式,这很有用,但Peter所描述的负向前瞻方法允许在单个字符串中同时存在正向和负向条件,这对于作为更大表达式的一部分非常有用。 - Godeke
绝对正确。但问题是“匹配不包含特定字符序列的字符串”。我认为为此目的使用负向先行断言有些过度。 - user71268
2
如果您正在使用文本编辑器,则无法执行此操作。 - Jamel Toms
如果您在编程语言之外使用正则表达式,例如Apache或Nginx配置文件中,则此内容无用。 - mwieczorek

3

只需在字符串中搜索“ab”,然后取反结果:

!/ab/.test("bamboo"); // true
!/ab/.test("baobab"); // false

看起来更简单,而且应该更快。


2
正则表达式 [^ab] 会匹配例如 'ab ab ab ab',但不会匹配 'ab',因为它只会匹配字符串中的 ' a' 或 'b '。
您使用的是哪种语言/场景?您能从原始集合中减去结果,仅匹配 'ab' 吗?
如果您正在使用 GNU grep 并解析输入,请使用 '-v' 标志来反转结果,返回所有非匹配项。其他正则表达式工具也有“返回非匹配项”的功能。
如果我理解正确,您想要除了包含任何位置的 'ab' 的项之外的所有内容。

2
正则表达式 [^ab] 会匹配例如 'ab ab ab ab',但不会匹配 'ab',因为它会在字符串 ' a' 或 'b ' 上匹配。这似乎是不正确的。[^ab] 是一个字符类,它匹配除了a和b之外的所有内容。显然,它将匹配空格。 - Scratte

2
在这种情况下,我可能会完全避免使用正则表达式,而选择像这样的东西:
if (StringToTest.IndexOf("ab") < 0)
  //do stuff

这种方法可能会更快(与上面的正则表达式相比,快速测试显示该方法只需要约25%的时间)。一般来说,如果我知道我要查找的确切字符串,我发现正则表达式过于复杂。由于您知道不想要“ab”,因此可以简单地测试字符串是否包含该字符串,而无需使用正则表达式。


这是一个很好的观点!如果序列是一个简单的字符串,那么正则表达式会使事情变得过于复杂;包含/ indexOf 检查是更明智的选择。 - Peter Boughton

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