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

Regular expression to match a line that doesn't contain a word

提问人: 提问时间:1/2/2009 最后编辑:25 revs, 19 users 14%knaser 更新时间:11/11/2023 访问量:4992552

问:

我知道可以匹配一个单词,然后使用其他工具(例如)。但是,是否可以匹配不包含特定单词的行,例如,使用正则表达式?grep -vhede

输入:
hoho
hihi
haha
hede
法典:
grep "<Regex for 'doesn't contain hede'>" input
期望输出:
hoho
hihi
haha
正则表达式否定

评论

105赞 stevendesu 9/29/2011
可能晚了几年,但有什么问题:?这个想法很简单。保持匹配,直到看到不需要的字符串的开头,然后仅在字符串未完成的 N-1 情况下匹配(其中 N 是字符串的长度)。这些 N-1 情况是“h 后跟非 e”、“he 后跟非 d”和“hed 后跟非 e”。如果您设法通过了这些 N-1 案例,则您成功地与不需要的字符串不匹配,因此您可以重新开始查找([^h]*(h([^e]|$)|he([^d]|$)|hed([^e]|$)))*[^h]*
430赞 Peter Schuetze 1/31/2012
@stevendesu:尝试“非常非常长的单词”,甚至更好的半句话。玩得开心打字。顺便说一句,它几乎不可读。不知道性能影响。
14赞 stevendesu 2/2/2012
@PeterSchuetze:当然,对于非常非常长的单词来说,这并不漂亮,但这是一个可行且正确的解决方案。虽然我没有对性能进行测试,但我不认为它会太慢,因为大多数后一条规则都会被忽略,直到你看到一个 h(或单词、句子等的第一个字母)。您可以使用迭代串联轻松为长字符串生成正则表达式字符串。如果它有效并且可以快速生成,那么易读性重要吗?这就是评论的用途。
66赞 jaytea 9/10/2012
@stevendesu:我甚至更晚,但这个答案几乎完全是错误的。首先,它要求主题包含它不应该包含的“h”,因为任务是“匹配不包含特定单词的行”。让我们假设您打算使内部组成为可选的,并且该模式是锚定的:当“hede”的实例前面有“hede”的部分实例(例如“hhede”)时,这将失败。^([^h]*(h([^e]|$)|he([^d]|$)|hed([^e]|$))?)*$
20赞 aliteralmind 4/10/2014
此问题已添加到 Stack Overflow 正则表达式常见问题解答的“高级正则表达式 Fu”下。

答:

247赞 Athena #1

如果你只是将它用于 grep,你可以用它来获取所有不包含 hede 的行。grep -v hede

ETA:哦,重读这个问题,可能就是你所说的“工具选项”的意思。grep -v

评论

31赞 Olivier Lalonde 5/6/2014
提示:要逐步过滤掉不想要的内容:grep -v “hede” |grep -v “嗨” |...等。
62赞 Olaf Dietsche 4/26/2015
或者只使用一个进程grep -v -e hede -e hihi -e ...
24赞 Putnik 12/9/2016
或者只是:)grep -v "hede\|hihi"
7赞 codeforester 3/12/2018
如果要过滤掉许多模式,请将它们放在文件中并使用grep -vf pattern_file file
15赞 Amit Naidu 6/3/2018
或者干脆或者为了避免尴尬的逃避。egrepgrep -Ev "hede|hihi|etc"
66赞 Josh Lee #2

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

评论

12赞 FGM 8/7/2012
一些工具,特别是 mysqldumpslow,只提供这种过滤数据的方式,所以在这种情况下,找到一个正则表达式来做到这一点是除了重写工具之外最好的解决方案(MySQL AB / Sun / Oracle 尚未包含为此提供的各种补丁。
1赞 Henno Vermeulen 10/18/2013
与我的情况完全相似。Velocity 模板引擎使用正则表达式来决定何时应用转换(转义 html),我希望它始终工作,除非在一种情况下。
1赞 kingfrito_5005 10/21/2016
还有什么选择?除了正则表达式之外,我从未遇到过任何可以进行精确字符串匹配的东西。如果 OP 使用编程语言,可能还有其他工具可用,但如果他/她使用不编写代码,可能没有其他选择。
2赞 LarsH 12/6/2016
正则表达式是最佳选择的众多非假设场景之一:我在一个显示日志输出的 IDE (Android Studio) 中,提供的唯一过滤工具是:纯字符串和正则表达式。尝试使用纯字符串执行此操作将完全失败。
7342赞 12 revs, 7 users 77%Bart Kiers #3

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

^((?!hede).)*$

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

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

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

或内联使用它:

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

(其中 /.../ 是正则表达式分隔符,即不是模式的一部分)

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

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

解释

字符串只是一个字符列表。在每个字符之前和之后,都有一个空字符串。因此,字符列表将包含空字符串。考虑字符串:nnn+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

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

因此,在我的示例中,首先验证每个空字符串,以查看前面是否没有 up,然后 (dot) 使用字符。正则表达式只会这样做一次,因此它被包装在一个组中,并重复零次或多次:.最后,锚定输入的开始和结束,以确保整个输入被消耗:"hede".(?!hede).((?!hede).)*^((?!hede).)*$

正如你所看到的,输入将失败,因为 on ,正则表达式失败(前面"ABhedeCD"e3(?!hede)"hede"

评论

43赞 Archimaredes 3/4/2016
我不会说这是正则表达式不擅长的事情。此解决方案的便利性非常明显,与程序化搜索相比,性能下降通常并不重要。
54赞 Peter K 11/18/2016
严格来说,负 loook-ahead 会使你成为正则表达式不规则。
97赞 Bart Kiers 11/18/2016
@PeterK,当然,但这是 SO,而不是 MathOverflow 或 CS-Stackexchange。在这里提出问题的人通常都在寻找一个实用的答案。大多数支持正则表达式的库或工具(如 OP 提到的)都具有理论意义上的非常规功能。grep
25赞 Peter K 11/18/2016
@Bart Kiers,没有冒犯你的答案,只是这种术语的滥用让我有点恼火。这里真正令人困惑的部分是,严格意义上的正则表达式可以很好地做 OP 想要的事情,但编写它们的通用语言不允许这样做,这导致了(数学上丑陋的)解决方法,例如前瞻。请参阅下面的答案和我在那里的评论,了解(理论上对齐的)正确的方法。毋庸置疑,它在大输入上工作得更快。
24赞 baldrs 11/24/2016
如果您想知道如何在 vim 中执行此操作:^\(\(hede\)\@!.\)*$
36赞 3 revs, 3 users 77%kiwalk #4

不是正则表达式,但我发现使用带有管道的串行 greps 来消除噪音是合乎逻辑且有用的。

例如。搜索一个没有所有注释的 Apache 配置文件 -

grep -v '\#' /opt/lampp/etc/httpd.conf      # this gives all the non-comment lines

grep -v '\#' /opt/lampp/etc/httpd.conf |  grep -i dir

串行 grep 的逻辑是 (不是注释) 和 (匹配 dir)

评论

2赞 Angel.King.47 7/12/2011
我认为他要求的是正则表达式版本的grep -v
9赞 Xavi Montero 3/2/2013
这很危险。还错过了像这样的台词good_stuff #comment_stuff
959赞 3 revs, 2 users 69%FireCoding #5

请注意,解决方案不是以“hede”开头的:

^(?!hede).*$

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

^((?!hede).)*$

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

评论

0赞 Alex 6/26/2015
^((?!hede).)*$使用 jQuery DataTable 插件从数据集中排除字符串对我有用
5赞 Aleks Ya 10/19/2015
你好!我无法撰写不以“hede”则表达式结尾。你能帮忙吗?
3赞 Nyerguds 5/4/2016
@AleksYa:只需使用“contain”版本,并将结束锚点包含在搜索字符串中:将字符串从“hede”更改为“hede$”
7赞 thisismydesign 9/15/2017
@AleksYa:不结束版本可以使用负后视来完成,如下所示:。@Nyerguds的版本也可以工作,但完全错过了答案提到的性能要点。(.*)(?<!hede)$
11赞 JackPRead 1/15/2019
为什么有这么多答案?使用起来不是更有效率吗?它做同样的事情,但步骤更少^((?!hede).)*$^(?!.*hede).*$
117赞 Hades32 #6

给出的答案很好,只是一个学术观点:

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

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

这只做一个完整的匹配。在子比赛中这样做甚至会更加尴尬。

评论

1赞 Steve-o 2/20/2014
需要注意的是,这仅使用基本的 POSIX.2 正则表达式,因此,当 PCRE 不可用时,简洁的正则更可移植。
7赞 ThomasMcLeod 3/23/2014
我同意。许多(如果不是大多数)正则表达式不是常规语言,无法被有限自动机识别。
0赞 James Haigh 6/14/2014
@ThomasMcLeod,Hades32:能够在任何可能的常规语言的范围内,能够说“”和“”以及诸如“”之类的表达的“”吗?(这可能是CS的问题。(hede|Hihi)
8赞 James Haigh 6/14/2014
@JohnAllen:!!......好吧,不是实际的正则表达式,而是学术参考,这也与计算复杂性密切相关;从根本上说,PCRE不能保证与POSIX正则表达式相同的效率。
4赞 Falco 8/13/2014
对不起 - 这个答案根本不起作用,它会匹配呵呵,甚至部分匹配呵呵(后半部分)
20赞 6 revs, 4 users 84%diyism #7

如果要匹配字符以否定类似于否定字符类的单词:

例如,字符串:

<?
$str="aaa        bbb4      aaa     bbb7";
?>

请勿使用:

<?
preg_match('/aaa[^bbb]+?bbb7/s', $str, $matches);
?>

用:

<?
preg_match('/aaa(?:(?!bbb).)+?bbb7/s', $str, $matches);
?>

notice 既不是 lookbackward 也不是 lookahead,它是 lookcurrent,例如:"(?!bbb)."

"(?=abc)abcde", "(?!abc)abcde"

评论

3赞 Didier L 5/22/2012
perl 正则表达式中没有“lookcurrent”。这确实是一个负面的展望(前缀)。正前视的前缀将是,而相应的后视前缀将是 和。前瞻意味着您在不消耗它们的情况下阅读下一个字符(因此是“前面”)。回溯意味着您检查已使用的字符。(?!(?=(?<!(?<=
0赞 Scratte 2/5/2021
不知道怎么说到底有什么意义。(?!abc)abcde
79赞 9 revsRoy Tinker #8

如果希望正则表达式测试在整个字符串匹配时失败,则以下方法将起作用:

^(?!hede$).*

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

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

myStr !== 'foo'

如果您需要任何正则表达式功能(此处为不区分大小写和范围匹配),您甚至可以将否定放在测试之外

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

但是,在需要正则表达式测试(可能通过 API)的情况下,此答案顶部的正则表达式解决方案可能会有所帮助。

评论

0赞 eagor 5/12/2017
尾随空格呢?例如,如果我希望测试失败与字符串?" hede "
0赞 Roy Tinker 5/13/2017
@eagor指令与单个空格字符匹配\s
0赞 eagor 5/14/2017
谢谢,但我没有设法更新正则表达式来完成这项工作。
3赞 Roy Tinker 5/16/2017
@eagor:^(?!\s*hede\s*$).*
31赞 11 revs, 2 users 96%Casimir et Hippolyte #9

这样,您就可以避免对每个仓位进行前瞻测试:

/^(?:[^h]+|h++(?!ede))*+$/

等效于(对于 .NET):

^(?>(?:[^h]+|h+(?!ede))*)$

旧答案:

/^(?>[^h]+|h+(?!ede))*$/

评论

8赞 Alan Moore 4/14/2013
好点子;我很惊讶以前没有人提到这种方法。但是,当应用于不匹配的文本时,该特定正则表达式容易发生灾难性的回溯。我是这样做的:/^[^h]*(?:h+(?!ede)[^h]*)*$/
0赞 Alan Moore 4/15/2013
...或者你可以让所有的量词都具有所有格。;)
0赞 ridgerunner 12/20/2013
@Alan Moore - 我也很惊讶。只有在下面的答案中发布了相同的模式后,我才在这里看到了您的评论(以及堆中最好的正则表达式)。
0赞 Qtax 2/20/2014
@ridgerunner,不一定是最好的。我见过一些基准测试,其中顶级答案表现更好。(我对此感到惊讶。
15赞 2 revsKevin Fegan #10

OP 没有指定或标记帖子以指示正则表达式将在其中使用的上下文(编程语言、编辑器、工具)。

对我来说,我有时需要在使用文本板编辑文件时执行此操作。

Textpad 支持某些正则表达式,但不支持前瞻或后视,因此需要几个步骤。

如果我希望保留所有包含字符串hede的行,我会这样做:

1.搜索/替换整个文件,在包含任何文本的每行的开头添加一个唯一的“标签”。

    Search string:^(.)  
    Replace string:<@#-unique-#@>\1  
    Replace-all  

2. 删除所有包含字符串 hede 的行(替换字符串为空):

    Search string:<@#-unique-#@>.*hede.*\n  
    Replace string:<nothing>  
    Replace-all  

3.此时,所有剩余的行都不包含字符串hede。从所有行中删除唯一的“标签”(替换字符串为空):

    Search string:<@#-unique-#@>
    Replace string:<nothing>  
    Replace-all  

现在,您已经拥有了原始文本,其中删除了所有包含字符串 hede 的行。


如果我希望只对包含字符串 hede 的行执行其他操作,我会这样做:

1.搜索/替换整个文件,在包含任何文本的每行的开头添加一个唯一的“标签”。

    Search string:^(.)  
    Replace string:<@#-unique-#@>\1  
    Replace-all  

2. 对于包含字符串 hede 的所有行,删除唯一的“Tag”:

    Search string:<@#-unique-#@>(.*hede)
    Replace string:\1  
    Replace-all  

3.此时,所有以唯一“Tag”开头的行,不要包含字符串hede。我现在只能对这些行做其他事情

4. 完成后,我从所有行中删除唯一的“标签”(替换字符串为空):

    Search string:<@#-unique-#@>
    Replace string:<nothing>  
    Replace-all  
24赞 ridgerunner #11

我是这样做的:

^[^h]*(h(?!ede)[^h]*)*$

比其他答案更准确、更有效。它实现了 Friedl 的“展开循环”效率技术,并且需要更少的回溯。

评论

0赞 Jon Grah 11/12/2022
如果搜索词包含另外 2 个相同的首字母怎么办?喜欢或??hhedehedhe
243赞 3 revs, 2 users 72%Jessica #12

答:

^((?!hede).)*$

解释:

^字符串的开头,分组并捕获到 \1(0 次或更多次(匹配尽可能多的数量)),
向前看是否有,
((?!

hede你的字符串,

)look-ahead 的结尾,除 \n 以外的任何字符,\1 的结尾(注意:因为您在此捕获中使用量词,所以只有捕获模式的最后一次重复将存储在 \1 中)
在可选的 \n 之前,
以及字符串的末尾
.)*$

评论

21赞 Damodar Bashyal 8/11/2015
太棒了,在使用多个单词的崇高文本 2 中为我工作'^((?!DSAU_PW8882WEB2|DSAU_PW8884WEB2|DSAU_PW8884WEB).)*$'
8赞 2 revs, 2 users 98%Kaz #13

TXR 语言支持正则表达式否定。

$ txr -c '@(repeat)
@{nothede /~hede/}
@(do (put-line nothede))
@(end)'  Input

一个更复杂的例子:匹配所有以 开头和结尾的行,但不包含子字符串:azhede

$ txr -c '@(repeat)
@{nothede /a.*z&~.*hede.*/}
@(do (put-line nothede))
@(end)' -
az         <- echoed
az
abcz       <- echoed
abcz
abhederz   <- not echoed; contains hede
ahedez     <- not echoed; contains hede
ace        <- not echoed; does not end in z
ahedz      <- echoed
ahedz

正则表达式否定本身并不是特别有用,但是当你也有交集时,事情就会变得有趣,因为你有一整套布尔集运算:你可以表示“与此匹配的集合,除了与此匹配的事物”。

评论

0赞 Wiktor Stribiżew 2/19/2018
请注意,它也是基于 ElasticSearch Lucene 的正则表达式的解决方案。
72赞 2 revs, 2 users 98%amobiz #14

对于负前瞻,正则表达式可以匹配不包含特定模式的内容。巴特·基尔斯(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赞 S.Serpooshan 3/1/2017
因此,要简单地检查给定的字符串是否不包含 str1 和 str2:^(?!.*(str1|str2)).*$
3赞 amobiz 3/2/2017
是的,或者您可以使用惰性量词: ,具体取决于您的数据。添加了,因为我们不需要捕获它。^(?!.*?(?:str1|str2)).*$?:
1赞 user5389726598465 7/23/2017
这是迄今为止最好的答案,是 10xms 的倍数。如果你在答案上添加了你的 jsfiddle 代码和结果,人们可能会注意到它。我想知道为什么没有hede时,懒惰的版本比贪婪的版本快。他们不应该花同样的时间吗?
0赞 amobiz 8/3/2017
是的,它们花费的时间相同,因为它们都测试了整个文本。
0赞 Falco 11/18/2022
@user5389726598465,由于引擎中的底层实现和优化,延迟版本很可能更快。计算机通常擅长从头到尾线性访问数据,缓存和分支预测可以针对这种访问进行优化。
53赞 4 revsFalco #15

基准

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

基准文本:

前 7 行不应匹配,因为它们包含搜索的 Expression,而后面的 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赞 ikegami 8/23/2016
你也应该有时间。此外,最好将匹配语料库和非匹配语料库的表达式分开排名,因为通常是大多数行匹配或大多数行不匹配的情况。^(?!.*hede)
12赞 2 revsAvinash Raj #16

通过 PCRE 动词 (*SKIP)(*F)

^hede$(*SKIP)(*F)|^.*$

这将完全跳过包含确切字符串的行并匹配所有剩余的行。hede

演示

部分的执行:

让我们通过将上面的正则表达式分成两部分来考虑它。

  1. 符号前的部分。零件不应匹配|

    ^hede$(*SKIP)(*F)
    
  2. 符号后面的部分。零件应匹配|

    ^.*$
    

第 1 部分

正则表达式引擎将从第一部分开始执行。

^hede$(*SKIP)(*F)

解释:

  • ^断言我们才刚刚开始。
  • hede匹配字符串hede
  • $断言我们处于行的末端。

因此,包含字符串的行将被匹配。一旦正则表达式引擎看到以下内容(注意:您可以将 (*F) 写为 (*FAIL)) 动词,它就会跳过并使匹配失败。 称为更改或逻辑 OR 运算符添加到 PCRE 动词旁边,该动词反过来匹配所有行上每个字符之间存在的所有边界,但该行包含确切的字符串。请在此处查看演示。也就是说,它尝试匹配剩余字符串中的字符。现在,第二部分中的正则表达式将被执行。hede(*SKIP)(*F)|hede

第 2 部分

^.*$

解释:

  • ^断言我们才刚刚开始。即,它匹配除行中的行开始之外的所有行开始。请在此处查看演示。hede
  • .*在多行模式下,将匹配除换行符或回车符以外的任何字符。并且会重复前一个字符零次或多次。所以会匹配整条线。请在此处查看演示。.*.*

    嘿,为什么你添加了 .* 而不是 .+ ?

    因为会匹配空行,但不会匹配空行。我们要匹配除 之外的所有行,输入中也可能有空行。所以你必须用代替 . 将重复前一个字符一次或多次。请参阅此处匹配空行。.*.+hede.*.+.+.*

  • $此处不需要线尾锚点。

8赞 2 revs, 2 users 80%andrew pate #17

在代码中使用两个正则表达式可能更易于维护,一个用于执行第一个匹配,然后如果它匹配,则运行第二个正则表达式以检查要阻止的异常情况,例如,然后在代码中使用适当的逻辑。^.*(hede).*

好的,我承认这并不是对所发布问题的真正答案,它也可能比单个正则表达式使用更多的处理。但是,对于来到这里寻找异常情况的快速紧急修复的开发人员来说,这个解决方案不容忽视。

69赞 6 revs, 4 users 96%akim #18

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

Vcsn 支持此运算符(它表示 postfix)。{c}

你首先定义表达式的类型:标签是字母(),例如,从中选择(当然,在使用补语时定义字母表非常重要),并且为每个单词计算的“值”只是一个布尔值:单词被接受,被拒绝。lal_charaztruefalse

在 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

The corresponding automaton

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

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赞 reinierpost 11/9/2015
没错,但很丑陋,只适用于小字符集。你不想用Unicode字符串这样做:-)
0赞 Pedro Gimeno 12/7/2016
正则表达式对我不起作用,使用 .它匹配 .我也试过把它锚定到开头和结尾,但仍然没有用。()|h(ed?)?|([^h]|h([^e]|e([^d]|d([^e]|e.)))).*egrephede
3赞 akim 12/8/2016
@PedroGimeno 当你锚定时,你确保把这个正则表达式放在第一位吗?否则,锚点之间的优先级将无法很好地播放。.|'^(()|h(ed?)?|([^h]|h([^e]|e([^d]|d([^e]|e.)))).*)$'
0赞 Pedro Gimeno 12/9/2016
@akim 这似乎是问题所在,谢谢和抱歉(请参阅我的答案以获取完整的子字符串匹配)。忘了说,这张图在任何地方都没有 [^d]。我怀疑这是一个错误。
4赞 Pedro Gimeno 1/29/2019
我认为值得一提的是,这种方法用于匹配不是“hede”一词的行,而不是不包含“hede”一词的行,这是 OP 所要求的。请参阅我对后者的回答。
30赞 2 revsikegami #19

前面提到的很棒,因为它可以锚定。(?:(?!hede).)*

^(?:(?!hede).)*$               # A line without hede

foo(?:(?!hede).)*bar           # foo followed by bar, without hede between them

但在这种情况下,以下内容就足够了:

^(?!.*hede)                    # A line without hede

这种简化已准备好添加“AND”子句:

^(?!.*hede)(?=.*foo)(?=.*bar)   # A line with foo and bar, but without hede
^(?!.*hede)(?=.*foo).*bar       # Same
3赞 3 revs, 2 users 98%JohnP2 #20

一个更简单的解决方案是使用 not 运算符

if 语句需要匹配 “contains” 而不是 “excludes”。

var contains = /abc/;
var excludes =/hede/;

if(string.match(contains) && !(string.match(excludes))){  //proceed...

我相信正则表达式的设计者预料到会使用非运算符。

37赞 10 revs, 2 users 99%Pedro Gimeno #21

由于没有其他人直接回答所提出的问题,所以我会这样做。

答案是,使用 POSIX ,不可能从字面上满足此请求:grep

grep "<Regex for 'doesn't contain hede'>" input

原因是在没有标志的情况下,POSIX 只需要使用基本正则表达式 (BRE),由于子表达式中缺乏交替,这些正则表达式根本不足以完成该任务。它支持的唯一一种交替涉及提供多个用换行符分隔的正则表达式,这并不涵盖所有常规语言,例如,没有与扩展正则表达式 (ERE) 匹配相同常规语言的有限 BRE 集合。grep^(ab|cd)*$

但是,GNU 实现了允许它的扩展。特别是,是 GNU 实现 BRE 中的交替运算符。如果您的正则表达式引擎支持交替、括号和 Kleene 星号,并且能够锚定到字符串的开头和结尾,那么这就是此方法所需的全部内容。但请注意,除了负集之外,负集也非常方便,因为否则,您需要将它们替换为列出不在集合中的每个字符的表单表达式,这非常繁琐且过长,如果整个字符集是 Unicode,则更是如此。grep\|[^ ... ](a|b|c| ... )

多亏了形式语言理论,我们才能看到这样的表达方式。对于GNU,答案是这样的:grep

grep "^\([^h]\|h\(h\|eh\|edh\)*\([^eh]\|e[^dh]\|ed[^eh]\)\)*\(\|h\(h\|eh\|edh\)*\(\|e\|ed\)\)$" input

(在Grail中找到,并手工进行了一些进一步的优化)。

您还可以使用实现 ERE 的工具,例如 ,来摆脱反斜杠,或者等效地将标志传递给 POSIX(尽管我的印象是该问题需要避免任何标志):egrep-Egrepgrep

egrep "^([^h]|h(h|eh|edh)*([^eh]|e[^dh]|ed[^eh]))*(|h(h|eh|edh)*(|e|ed))$" input

下面是一个测试它的脚本(请注意,它会在当前目录中生成一个文件)。其他答案中提出的几个表达式未通过此测试。testinput.txt

#!/bin/bash
REGEX="^\([^h]\|h\(h\|eh\|edh\)*\([^eh]\|e[^dh]\|ed[^eh]\)\)*\(\|h\(h\|eh\|edh\)*\(\|e\|ed\)\)$"

# First four lines as in OP's testcase.
cat > testinput.txt <<EOF
hoho
hihi
haha
hede

h
he
ah
head
ahead
ahed
aheda
ahede
hhede
hehede
hedhede
hehehehehehedehehe
hedecidedthat
EOF
diff -s -u <(grep -v hede testinput.txt) <(grep "$REGEX" testinput.txt)

在我的系统中,它打印:

Files /dev/fd/63 and /dev/fd/62 are identical

不出所料。

对于那些对细节感兴趣的人,所采用的技术是将与单词匹配的正则表达式转换为有限自动机,然后通过将每个接受状态更改为不接受来反转自动机,反之亦然,然后将生成的 FA 转换回正则表达式。

正如大家所指出的,如果你的正则表达式引擎支持负前瞻,那么正则表达式要简单得多。例如,使用 GNU grep:

grep -P '^((?!hede).)*$' input

但是,这种方法的缺点是需要回溯正则表达式引擎。这使得它不适合使用安全正则表达式引擎(如 RE2)的安装,这是在某些情况下更喜欢生成方法的原因之一。

使用 Kendall Hopkins 用 PHP 编写的优秀 FormalTheory 库,它提供了类似于 Grail 的功能,以及我自己编写的简化器,我已经能够编写一个给定输入短语的在线负正则表达式生成器(目前仅支持字母数字和空格字符,并且长度有限):http://www.formauri.es/personal/pgimeno/misc/non-match-regex/

因为它输出:hede

^([^h]|h(h|e(h|dh))*([^eh]|e([^dh]|d[^eh])))*(h(h|e(h|dh))*(ed?)?)?$

这相当于上述。

6赞 3 revs, 3 users 89%Daniel Nyamasyo #22

以下功能将帮助您获得所需的输出

<?PHP
      function removePrepositions($text){
            
            $propositions=array('/\bfor\b/i','/\bthe\b/i'); 
        
            if( count($propositions) > 0 ) {
                foreach($propositions as $exceptionPhrase) {
                    $text = preg_replace($exceptionPhrase, '', trim($text));

                }
            $retval = trim($text);

            }
        return $retval;
    }
     
        
?>
13赞 aelor #23

自从 ruby-2.4.1 引入以来,我们可以在 Ruby 的正则表达式中使用新的 Absent 运算符

来自官方文档

(?~abc) matches: "", "ab", "aab", "cccc", etc.
It doesn't match: "abc", "aabc", "ccccabc", etc.

因此,在您的情况下,您可以完成这项工作^(?~hede)$

2.4.1 :016 > ["hoho", "hihi", "haha", "hede"].select{|s| /^(?~hede)$/.match(s)}
 => ["hoho", "hihi", "haha"]
3赞 jaytea #24

如何使用 PCRE 的回溯控制动词来匹配不包含单词的行

这是我以前从未见过使用过的方法:

/.*hede(*COMMIT)^|/

运作方式

首先,它试图在行中的某个地方找到“hede”。如果成功,此时会告诉引擎,不仅在发生故障时不回溯,而且在这种情况下也不要尝试任何进一步的匹配。然后,我们尝试匹配不可能匹配的东西(在本例中为 )。(*COMMIT)^

如果一行不包含“hede”,则第二个备选方案(空子模式)成功匹配主题字符串。

这种方法并不比消极的展望更有效,但我想我会把它放在这里,以防有人觉得它很漂亮,并发现它用于其他更有趣的应用程序。

2赞 2 revs, 2 users 96%BrunoF #25

也许你会在 Google 上找到这一点,同时尝试编写一个能够匹配不包含子字符串的行段(而不是整行)的正则表达式。花了一段时间才弄清楚,所以我会分享:

给定一个字符串:

<span class="good">bar</span><span class="bad">foo</span><span class="ugly">baz</span>

我想匹配不包含子字符串“bad”的标签。<span>

/<span(?:(?!bad).)*?>将匹配 和 。<span class=\"good\"><span class=\"ugly\">

请注意,括号有两组(层):

  • 最里面的一个是负面的展望(它不是一个捕获组)
  • Ruby 将最外层解释为捕获组,但我们不希望它成为捕获组,所以我在它的开头添加了 ?:,它不再被解释为捕获组。

Ruby 演示:

s = '<span class="good">bar</span><span class="bad">foo</span><span class="ugly">baz</span>'
s.scan(/<span(?:(?!bad).)*?>/)
# => ["<span class=\"good\">", "<span class=\"ugly\">"]
1赞 Donald #26

使用 ConyEdit,可以使用命令行获取不包含正则表达式匹配的行,或使用命令行删除包含正则表达式匹配的行。他们有相同的结果。cc.gl !/hede/cc.dl /hede/

评论

0赞 tripleee 3/21/2023
类似,但不那么晦涩难懂,或者awk '!/hede/'grep -v 'hede'
28赞 5 revs, 2 users 54%Dannie P #27

在我看来,顶级答案的可读性更强的变体:

^(?!.*hede)

基本上,“当且仅当行中没有'hede'时,才在行的开头匹配”——所以这个要求几乎直接转化为正则表达式。

当然,可能有多个故障要求:

^(?!.*(hede|hodo|hada))

详:^ 锚点确保正则表达式引擎不会在字符串中的每个位置重试匹配,这将匹配每个字符串。

开头的 ^ 锚点表示行的开头。grep 工具一次匹配一行,在处理多行字符串的上下文中,可以使用“m”标志:

/^(?!.*hede)/m # JavaScript syntax

(?m)^(?!.*hede) # Inline flag

评论

0赞 Bernardo Dal Corno 8/21/2019
与顶级答案的一个区别是,这与任何内容都不匹配,如果没有“hede”,则与整行匹配
1赞 Falco 11/18/2022
@BernardoDalCorno 这可以通过添加到表达式中来轻松更改:然后匹配将包含所有文本。.*^(?!.*hede).*
0赞 Falco 11/18/2022
这个答案似乎是 JavaScript 最有效的答案,因为所有其他答案都会在非常大的输入中遇到“超出最大调用堆栈大小”。这个答案没有使用任何组,只是一个简单的展望。
5赞 2 revs, 2 users 67%cloudhopperpilot #28

^((?!hede).)*$是一个优雅的解决方案,除了因为它消耗字符,因此您将无法将其与其他标准组合。例如,假设您想检查“hede”是否存在和“haha”是否存在。此解决方案将起作用,因为它不会消耗字符:

^(?!.*\bhede\b)(?=.*\bhaha\b) 
21赞 2 revs, 2 users 97%Emma #29

另一种选择是,要添加一个积极的前瞻并检查是否在输入行中的任何位置,那么我们将用类似于以下内容的表达式来否定它:hede

^(?!(?=.*\bhede\b)).*$

有词边界。


如果您想探索/简化/修改表达式,请在 regex101.com 的右上角面板上解释该表达式,在此链接中,您可以根据需要查看它如何与一些示例输入匹配。


正则表达式电路

jex.im 可视化正则表达式:

enter image description here

评论

5赞 Scratte 2/5/2021
我不明白“内在”的积极展望有什么用。
4赞 Wiktor Stribiżew 10/22/2021
这是一个伪装的^(?!.*\bhede\b).*$
6赞 Matthew Rideout #30

我想添加另一个示例,如果您尝试匹配包含字符串 X 但不包含字符串 Y 的整行。

例如,假设我们想检查我们的 URL/字符串是否包含“tasty-treats”,只要它在任何地方都不包含“chocolate”。

这种正则表达式模式可以工作(在 JavaScript 中也有效)

^(?=.*?tasty-treats)((?!chocolate).)*$

(示例中的全局多行标志)

交互式示例:https://regexr.com/53gv4

比赛

(这些网址包含“美味佳肴”,也不包含“巧克力”)

  • example.com/tasty-treats/strawberry-ice-cream
  • example.com/desserts/tasty-treats/banana-pudding
  • example.com/tasty-treats-overview

不匹配

(这些网址在某处包含“巧克力”——所以即使它们包含“美味佳肴”,它们也不会匹配)

  • example.com/tasty-treats/chocolate-cake
  • example.com/home-cooking/oven-roasted-chicken
  • example.com/tasty-treats/banana-chocolate-fudge
  • example.com/desserts/chocolate/tasty-treats
  • example.com/chocolate/tasty-treats/desserts
6赞 Anas R. #31

只要您正在处理线条,只需标记负匹配项并针对其余匹配项即可。

事实上,我在 sed 中使用这个技巧,因为它看起来不支持。^((?!hede).)*$

对于所需的输出

  1. 标记否定匹配项:(例如,带有 的行),使用完全不包含在全文中的字符。为此,表情符号可能是一个不错的选择。hede

    s/(.*hede)/🔒\1/g
    
  2. 定位其余部分(未标记的字符串:例如没有 )。假设您只想保留目标并删除其余目标(根据需要):hede

    s/^🔒.*//g
    

为了更好地理解

假设您要删除目标

  1. 标记否定匹配项:(例如,带有 的行),使用完全不包含在全文中的字符。为此,表情符号可能是一个不错的选择。hede

    s/(.*hede)/🔒\1/g
    
  2. 定位其余部分(未标记的字符串:例如没有 )。假设您要删除目标hede

    s/^[^🔒].*//g
    
  3. 删除标记:

    s/🔒//g
    
0赞 2 revs, 2 users 97%admin #32
# 一个简单的方式
import re
skip_word = 'hede'
stranger_char = '虩'
content = '''hoho
hihi
haha
hede'''
print(
    '\n'.join(re.findall(
        '([^{}]*?)\n'.format(stranger_char), 
        content.replace(skip_word, stranger_char)
    )).replace(stranger_char, skip_word) 
)

# hoho
# hihi
# haha
1赞 Benny #33

我能找到的最简单的事情是

[^(hede)]

https://regex101.com/ 下测试

还可以在该站点上添加单元测试用例

评论

0赞 JRichardsz 2/15/2023
不适用于可视代码
1赞 tripleee 3/21/2023
这只会查找一个不是 、 、 、 或 的字符。(hed)
0赞 Brandon Wegner #34

使用 (?<!hede) 是更好的答案。(?<!whateverYouDontWantToMatch) 是负面的,而不是 (?!whateverYouDontWantToMatch),这是对未来的消极展望。这意味着使用 (?<!) 它将在字符串的当前位置进行检查,而不仅仅是在匹配之后。举个例子。使用 (?!) 时会遇到问题,并且由于锚点的原因,它仅在这种情况下才有效。

0赞 profPlum #35

虽然你确实可以使用环顾四周,但我读了一篇文章,它使用了另一种看起来更优雅且语法更不繁琐的方法。

这个想法是违反直觉的:实际匹配你不想匹配的东西,而只匹配你想在组内匹配的东西,并在以后引用它们。

例如,将单词列入黑名单:然后只需使用 获取排除前缀单词的匹配项。pattern='\bTarzan\b|\bJane\b|(\w+)'group(1)'\1'

描述这一点的好文章:https://www.rexegg.com/regex-best-trick.html#simplecase & Great SO Answer,其中也描述了它:(*SKIP)或(*F)如何在正则表达式上工作?