正则表达式用于匹配不包含某个单词的行

5321

我知道可以使用其他工具(例如 grep -v)来匹配一个词并反转匹配结果。然而,是否可能使用正则表达式来匹配不包含特定单词(例如 hede)的行?

输入:
hoho
hihi
haha
hede
代码:
grep "<Regex for 'doesn't contain hede'>" input
期望的输出:
hoho
hihi
haha

105
也许有几年的延迟,但是这个正则表达式 ([^h]*(h([^e]|$)|he([^d]|$)|hed([^e]|$)))* 有什么问题吗?它的思想很简单。继续匹配直到看到不想要的字符串的开头,然后只在字符串未完成的N-1种情况下进行匹配(其中N为字符串的长度)。这N-1种情况是“h之后非e”,“he之后非d”和“hed之后非e”。如果你成功通过了这些N-1种情况,那么你就成功地没有匹配上不想要的字符串,所以你可以开始再次寻找[^h]* - stevendesu
430
尝试输入“a-very-very-long-word”或更好的方式是半个句子。打字愉快。顺便说一句,这几乎无法阅读。不清楚性能影响如何。 - Peter Schuetze
14
@PeterSchuetze:当然,对于非常长的单词来说,这种方法可能不太美观,但它是可行且正确的解决方案。虽然我没有测试过其性能,但我认为它不会太慢,因为大多数后面的规则都被忽略了,直到你看到一个h(或单词、句子等的第一个字母)。而且你可以很容易地使用迭代连接生成长字符串的正则表达式字符串。如果它有效且能够快速生成,那么可读性重要吗?对此可以在评论中进行说明。 - stevendesu
66
@stevendesu: 我来晚了,但是那个答案几乎完全错误。首先,它要求主题包含“h”,而实际上不应该要求,因为任务是“匹配不包含特定单词的行”。我们假设你的意思是使内部组变为可选的,并且该模式已经被锚定:^([^h]*(h([^e]|$)|he([^d]|$)|hed([^e]|$))?)*$这个模式在 "hhede" 等包含部分"hede"的实例之前出现时失败了。 - jaytea
20
这个问题已经被添加到Stack Overflow正则表达式常见问题解答下的“高级Regex-Fu”部分。 - aliteralmind
35个回答

7339

正则表达式不支持反向匹配的观念并非完全正确。您可以通过使用负面环视来模仿此行为:

^((?!hede).)*$

上述正则表达式将匹配任何不包含字符串'hede'的字符串或不带换行符的行。如前所述,这并不是正则表达式“擅长”的事情(也不应该这样做),但仍然是可能的

如果您需要匹配换行符,请使用DOT-ALL modifier(以下模式中的尾随s):

/^((?!hede).)*$/s

或者内联使用:

/(?s)^((?!hede).)*$/

(这里的/.../是正则表达式的分隔符,不是模式的一部分)

如果无法使用 DOT-ALL 修饰符,则可以通过字符类 [\s\S] 模拟相同的行为:

/^((?!hede)[\s\S])*$/

解释

一个字符串就是一个由n个字符组成的列表。在每个字符之前和之后都有一个空字符串。因此,一个由n个字符组成的列表将具有n+1个空字符串。考虑字符串"ABhedeCD"

    ┌──┬───┬──┬───┬──┬───┬──┬───┬──┬───┬──┬───┬──┬───┬──┬───┬──┐
S = │e1│ A │e2│ B │e3│ h │e4│ e │e5│ d │e6│ e │e7│ C │e8│ D │e9│
    └──┴───┴──┴───┴──┴───┴──┴───┴──┴───┴──┴───┴──┴───┴──┴───┴──┘
    
index    0      1      2      3      4      5      6      7

其中e是空字符串。正则表达式(?!hede).向前查找,看是否没有子字符串"hede",如果是这种情况(即看到其他东西),那么.(点)将匹配除换行符以外的任何字符。环视也称为零宽断言,因为它们不会消耗任何字符,只是断言/验证某些内容。

所以,在我的例子中,每个空字符串首先被验证,看是否有"hede",然后由.(点)消耗一个字符。正则表达式(?!hede).仅会执行一次,因此它被包装在一个组内,并重复零次或多次:((?!hede).)*。最后,将输入的起始位置和结束位置锚定,以确保整个输入都被消耗:^((?!hede).)*$

正如您所看到的,输入"ABhedeCD"会失败,因为在e3处,正则表达式(?!hede)失败了(有 "hede"在前面)。


43
我认为并不应过于指责正则表达式的能力。这种解决方案的方便性是相当明显的,与编程式搜索相比,性能损失通常不重要。 - Archimaredes
54
严格来说,负向先行断言会使您的正则表达式不再是“regular”(即正则的)。 - Peter K
97
@PeterK,好的,但这里是SO,不是MathOverflow或CS-Stackexchange。在这里提问的人通常寻求实际答案。大多数具有正则表达式支持的库或工具(例如OP提到的 grep)都具有使它们在理论上不是正则表达式的功能。 - Bart Kiers
25
@Bart Kiers,您的回答并没有冒犯我,只是这种术语的滥用让我有点恼火。真正令人困惑的部分在于,在严格意义上,正则表达式确实可以做到OP想要的,但是常用的编写语言不允许这样做,这导致了(在数学上丑陋的)解决方法,如前瞻。请参见下面的此答案和我在那里的评论,以获得(理论上对齐的)正确方法。不用说,它在大型输入上运行得更快。 - Peter K
24
如果你曾想知道如何在vim中实现以下操作:^\(\(hede\)\@!.\)*$ - baldrs
显示剩余25条评论

958

请注意解决方案非以"hede"开头

^(?!hede).*$

通常比解决不包含“hede”的方案要高效得多:

^((?!hede).)*$

前者仅检查输入字符串的第一个位置是否为“hede”,而不是每个位置。


^((?!hede).)*$ 在使用 jQuery DataTable 插件时,对于从数据集中排除一个字符串非常有效。 - Alex
5
你好!我无法编写不以"hede"结尾的正则表达式。你能帮忙吗? - Aleks Ya
3
请使用“包含”版本,并将结束锚点包括在搜索字符串中:将字符串从“hede”更改为“hede $”,以便使其不匹配。 - Nyerguds
7
@AleksYa提到,“不以hede结尾”的版本可以使用负回顾后断言来实现:(.*)(?<!hede)$。@Nyerguds的版本也可以工作,但完全忽视了答案提到的性能问题。 - thisismydesign
11
为什么有这么多回答都写着 ^((?!hede).)*$?使用 ^(?!.*hede).*$ 不是更高效吗?它可以用更少的步骤完成同样的操作。 - JackPRead
显示剩余2条评论

247

如果你只是用它来进行grep操作,你可以使用grep -v hede来获取所有不包含"hede"的行。

根据最新解释,"grep -v"很可能就是你所说的"工具选项"。


31
提示:逐步过滤掉不需要的内容:grep -v "hede" | grep -v "hihi" | ...等等。 - Olivier Lalonde
62
可以使用一个进程 grep -v -e hede -e hihi -e ... 进行操作。 - Olaf Dietsche
24
或者只需使用 grep -v "hede\|hihi" :)。 - Putnik
7
如果您有很多要过滤的模式,请将它们放入一个文件中,然后使用 grep -vf pattern_file file - codeforester
15
可以使用简单的 egrepgrep -Ev "hede|hihi|etc" 来避免繁琐的转义。 - Amit Naidu
1
egrep 命令示例: egrep -v "hede|hihi|etc" - Roald

243

答案:

^((?!hede).)*$

解释:

^表示字符串的开头, (分组并捕获到\1(0次或多次(尽可能匹配最多次数)), (?!向前查看,如果没有, hede你的字符串, )查找结束, .任何字符(除了\n), )*结束\1(注意:由于您在此捕获上使用了量词,因此仅存储捕获模式的最后一次重复), $位于可选的\n之前,并且是字符串的结尾


21
在Sublime Text 2中,使用多个单词'^((?!DSAU_PW8882WEB2|DSAU_PW8884WEB2|DSAU_PW8884WEB).)*$',非常有效。 - Damodar Bashyal

117

这些给出的答案完全没问题,只是有一个学术上的观点:

在理论计算机科学的意义上,正则表达式不能像这样做。对于它们来说,应该看起来像这样:

^([^h].*$)|(h([^e].*$|$))|(he([^h].*$|$))|(heh([^e].*$|$))|(hehe.+$) 

这只进行完全匹配。对于子匹配来说,这样做会更加棘手。


1
需要注意的是,这里仅使用基本的POSIX.2正则表达式,因此虽然简洁,但在PCRE不可用时更具可移植性。 - Steve-o
7
同意。许多正则表达式并非正则语言,不能被有限自动机识别。 - ThomasMcLeod
8
@JohnAllen:我!!!……好吧,不是实际的正则表达式,而是学术参考资料,它也与计算复杂度密切相关;PCREs基本上不能保证与POSIX正则表达式相同的效率。 - James Haigh
4
抱歉 - 这个答案并不准确,它会匹配到"hhehe",甚至会部分匹配到"hehe"(后半段)。 - Falco
这可以在语法上简化为 ^([^h].*|h([^e].*)?|he([^h].*)?|heh([^e].*)?|hehe.+)$^(([^h]|h[^e]|he[^h]|heh[^e]|hehe.).*|h|he|heh)$ - Bernhard Barker
显示剩余3条评论

79
如果您希望正则表达式测试仅在整个字符串匹配时失败,则可以使用以下方法:
^(?!hede$).*

例如-- 如果你想允许除 "foo" 之外的所有值(即 "foofoo","barfoo" 和 "foobar" 将通过,但 "foo" 将失败),则使用:^(?!foo$).*

当然,如果你正在检查 确切 相等性,在这种情况下更好的一般解决方案是检查字符串相等性,即

myStr !== 'foo'

如果需要使用正则表达式的功能(例如不区分大小写和范围匹配),您甚至可以将否定符号放在测试之外:

!/^[a-f]oo$/i.test(myStr)

这个答案顶部的正则表达式解决方案可能会有所帮助,特别是在需要进行正则表达式测试的情况下(例如通过API)。


尾随的空格怎么办?例如,如果我希望测试失败,并且字符串为" hede " - eagor
@eagor \s 指令匹配单个空格字符。 - Roy Tinker
谢谢,但我没有成功更新正则表达式使其工作。 - eagor
3
@eagor:^(?!\s*hede\s*$).* - Roy Tinker

72

使用负向先行断言,正则表达式可以匹配不包含特定模式的内容。这一点由Bart Kiers进行了回答和解释,讲得非常好!

但是,在Bart Kiers的答案中,先行断言部分将在匹配任何单个字符的同时测试1至4个字符。我们可以避免这种情况,让先行断言部分检查整个文本,确保不存在'hede',然后正常部分(.*)可以一次性吃掉整个文本。

以下是改进后的正则表达式:

/^(?!.*?hede).*$/

请注意,在负向前瞻部分的(*?)惰性量词是可选的,您也可以使用(*)贪婪量词,具体取决于您的数据:如果'hede'存在并且在文本的前一半,惰性量词可能更快;否则,贪婪量词会更快。但是如果'hede'不存在,则两者都会同样缓慢。

这里是演示代码

有关前瞻的更多信息,请查看优秀文章:精通前瞻和后顾

另外,请查看RegexGen.js,这是一个JavaScript正则表达式生成器,可帮助构建复杂的正则表达式。使用RegexGen.js,您可以以更易读的方式构造正则表达式:

var _ = regexGen;

var regex = _(
    _.startOfLine(),             
    _.anything().notContains(       // match anything that not contains:
        _.anything().lazy(), 'hede' //   zero or more chars that followed by 'hede',
                                    //   i.e., anything contains 'hede'
    ), 
    _.endOfLine()
);

4
为了简单检查给定的字符串是否不包含str1和str2:^(?!.*(str1|str2)).*$ - S.Serpooshan
3
可以使用懒惰量词:^(?!.*?(?:str1|str2)).*$,具体取决于您的数据。添加 ?: 是因为我们不需要捕获它。 - amobiz
1
这绝对是十倍于其他最佳答案的最佳答案。如果你把你的jsfiddle代码和结果添加到答案中,人们可能会注意到它。我想知道为什么在没有"hede"的情况下,懒惰版本比贪婪版本更快。难道它们不应该花费相同的时间吗? - user5389726598465
是的,它们需要相同的时间,因为它们都测试整个文本。 - amobiz
@user5389726598465,懒惰版本很可能更快,因为引擎中的底层实现和优化。计算机通常擅长从开始到结束线性访问数据,缓存和分支预测可能针对这种访问进行了优化。 - Falco

69

FWIW,由于正则语言(又称有理语言)在补集下是封闭的,因此总是可以找到一个正则表达式(又称有理表达式)来否定另一个表达式。但不是很多工具实现了这一点。

Vcsn 支持该运算符(它使用后缀标记为 {c})。

首先定义表达式的类型:标签为字母(lal_char),例如选择从az的字母(当使用补集时定义字母表是非常重要的),而每个单词计算的“值”仅是一个布尔值:true表示接受该单词,false表示拒绝。

在Python中:

In [5]: import vcsn
        c = vcsn.context('lal_char(a-z), b')
        c
Out[5]: {a,b,c,d,e,f,g,h,i,j,k,l,m,n,o,p,q,r,s,t,u,v,w,x,y,z} → 

接着您需要输入表达式:

In [6]: e = c.expression('(hede){c}'); e
Out[6]: (hede)^c

将此表达式转换为自动机:

In [7]: a = e.automaton(); a

相应的自动机

最后,将这个自动机转换为一个简单的表达式。

In [8]: print(a.expression())
        \e+h(\e+e(\e+d))+([^h]+h([^e]+e([^d]+d([^e]+e[^]))))[^]*

其中,+ 通常被表示为 |\e 表示空字,而 [^] 通常被写成 .(任何字符)。因此,稍微改写一下,可以得到正则表达式:()|h(ed?)?|([^h]|h([^e]|e([^d]|d([^e]|e.)))).*

你可以在这里查看这个示例,并在这里尝试 Vcsn 在线工具。


10
没错,但是这种方法只适用于小字符集,而且非常不美观。你肯定不想用在 Unicode 字符串上 :-) - reinierpost
正则表达式 ()|h(ed?)?|([^h]|h([^e]|e([^d]|d([^e]|e.)))).* 在使用 egrep 时对我无效。它匹配了 hede。我还尝试将其锚定到开头和结尾,但仍然无效。 - Pedro Gimeno
3
@PedroGimeno 当你使用锚点时,请确保首先将此正则表达式放在括号中。否则锚点和|之间的优先级将不会很好地配合。'^(()|h(ed?)?|([^h]|h([^e]|e([^d]|d([^e]|e.)))).*)$'。 - akim
@akim,那似乎就是问题所在了,谢谢并抱歉(请查看我的答案以获取完整的子字符串匹配)。还忘了说,图表中没有任何[^d]。我怀疑这是一个错误。 - Pedro Gimeno
@PedroGimeno 谢谢您指出这一点。当我第一次回答时,我读错了,并认为 hehe 是拒绝的意思。我已经更正了文本,但忘记更正快照。 - akim
4
我认为值得注意的是,这种方法是用于匹配不包含单词“hede”的行,而不是像OP所要求的那样不包含单词“hede”的行。请参阅我的答案以了解后者的解决方案。 - Pedro Gimeno

66

这里有一个很好的解释,说明为什么否定任意正则表达式并不容易。但是,我必须同意其他答案:如果这不是一个假设性的问题,那么在这里使用正则表达式并不是正确的选择。


12
一些工具,特别是mysqldumpslow,仅提供这种过滤数据的方式。因此,在这种情况下,找到一个正则表达式来完成这项任务是最好的解决方案,而不是重新编写该工具(有关此的各种补丁未被MySQL AB / Sun / Oracle收录)。 - FGM
1
与我的情况完全类似。Velocity模板引擎使用正则表达式来确定何时应用转换(转义HTML),但我希望它在除一种情况外始终有效。 - Henno Vermeulen
1
有什么替代方案吗?除了正则表达式,我从未遇到过能够进行精确字符串匹配的工具。如果 OP 使用编程语言,则可能有其他可用工具,但如果他/她不写代码,则可能没有其他选择。 - kingfrito_5005
2
在许多非假设情况下,正则表达式是最佳选择之一的编程场景:我在一个显示日志输出的IDE(Android Studio)中,提供的唯一过滤工具是:普通字符串和正则表达式。尝试使用普通字符串来完成这项任务将是完全失败的。 - LarsH

53

基准测试

我决定评估一些已经呈现的选项并比较它们的性能,以及使用一些新功能。 在.NET正则表达式引擎上进行基准测试:http://regexhero.net/tester/

基准文本:

前7行不应匹配,因为它们包含搜索表达式,而下面7行应该匹配!

Regex Hero is a real-time online Silverlight Regular Expression Tester.
XRegex Hero is a real-time online Silverlight Regular Expression Tester.
Regex HeroRegex HeroRegex HeroRegex HeroRegex Hero is a real-time online Silverlight Regular Expression Tester.
Regex Her Regex Her Regex Her Regex Her Regex Her Regex Her Regex Hero is a real-time online Silverlight Regular Expression Tester.
Regex Her is a real-time online Silverlight Regular Expression Tester.Regex Hero
egex Hero egex Hero egex Hero egex Hero egex Hero egex Hero Regex Hero is a real-time online Silverlight Regular Expression Tester.
RRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRegex Hero is a real-time online Silverlight Regular Expression Tester.

Regex Her
egex Hero
egex Hero is a real-time online Silverlight Regular Expression Tester.
Regex Her is a real-time online Silverlight Regular Expression Tester.
Regex Her Regex Her Regex Her Regex Her Regex Her Regex Her is a real-time online Silverlight Regular Expression Tester.
Nobody is a real-time online Silverlight Regular Expression Tester.
Regex Her o egex Hero Regex  Hero Reg ex Hero is a real-time online Silverlight Regular Expression Tester.

结果:

结果以每秒迭代次数为单位,作为3次运行的中位数 - 数字越大越好

01: ^((?!Regex Hero).)*$                    3.914   // Accepted Answer
02: ^(?:(?!Regex Hero).)*$                  5.034   // With Non-Capturing group
03: ^(?!.*?Regex Hero).*                   7.356   // Lookahead at the beginning, if not found match everything
04: ^(?>[^R]+|R(?!egex Hero))*$             6.137   // Lookahead only on the right first letter
05: ^(?>(?:.*?Regex Hero)?)^.*$             7.426   // Match the word and check if you're still at linestart
06: ^(?(?=.*?Regex Hero)(?#fail)|.*)$       7.371   // Logic Branch: Find Regex Hero? match nothing, else anything

P1: ^(?(?=.*?Regex Hero)(*FAIL)|(*ACCEPT))  ?????   // Logic Branch in Perl - Quick FAIL
P2: .*?Regex Hero(*COMMIT)(*FAIL)|(*ACCEPT) ?????   // Direct COMMIT & FAIL in Perl

因为 .NET 不支持动作动词 (*FAIL 等),所以我无法测试 P1 和 P2 的解决方案。

摘要:

总体上,最易读且在性能方面最快的解决方案似乎是使用简单负向先行断言的 03。这也是 JavaScript 最快的解决方案,因为 JS 不支持其他解决方案中更高级的正则表达式特性。


6
你也应该计时 ^(?!.*hede)。/// 另外,最好将匹配语料库和不匹配语料库的表达式分别排名,因为通常情况下大多数行是匹配或者不匹配的。 - ikegami

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