经常表达相匹配的一个线,不包含一个词
-
03-07-2019 - |
题
我知道这是可能的匹配一个词,然后反向的匹配使用其他工具(例如 grep -v
).然而,是否有可能匹配线没有包含一个具体的词语,例如 hede
, 使用一个经常的表达吗?
输入:
hoho
hihi
haha
hede
代码:
grep "<Regex for 'doesn't contain hede'>" input
期望输出:
hoho
hihi
haha
解决方案
正则表达式不支持逆匹配的概念并不完全正确。您可以使用负面外观来模仿此行为:
^((?!hede).)*$
上面的正则表达式将匹配任何字符串,或没有换行符的行,不包含(子)字符串'hede'。如上所述,这不是正则表达式<!>“好的<!>”; at(或应该),但仍然 可能。
如果您还需要匹配换行符,请使用 DOT-ALL修饰符(以下模式中的尾随s
):
/^((?!hede).)*$/s
或使用内联:
/(?s)^((?!hede).)*$/
(其中/.../
是正则表达式分隔符,即不属于模式的一部分)
如果DOT-ALL修饰符不可用,您可以使用字符类[\s\S]
模仿相同的行为:
/^((?!hede)[\s\S])*$/
说明
字符串只是n
字符列表。在每个字符之前和之后,都有一个空字符串。因此,n+1
字符列表将具有"ABhedeCD"
空字符串。考虑字符串e
:
┌──┬───┬──┬───┬──┬───┬──┬───┬──┬───┬──┬───┬──┬───┬──┬───┬──┐
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
其中(?!hede).
是空字符串。正则表达式"hede"
向前看,看看是否没有子字符串.
被看到,如果是这种情况(所以看到别的东西),那么((?!hede).)*
(点)将匹配除换行符之外的任何字符。环视也称为零宽度断言,因为它们不会消耗任何字符。他们只断言/验证某些东西。
因此,在我的示例中,在^((?!hede).)*$
(点)消耗字符之前,首先验证每个空字符串是否在前面没有e3
。正则表达式(?!hede)
将只执行一次,因此它被包装在一个组中,并重复零次或多次:<=>。最后,锚定输入的开始和结束以确保消耗整个输入:<=>
正如您所看到的,输入<=>将失败,因为在<=>上,正则表达式<=>失败( <=>提前!)。
其他提示
请注意的解决方案不以开始<!>#8220; hede <!>#8221; :
^(?!hede).*$
通常比不包含 <!>的解决方案更有效率#8220; hede <!>#8221; :
^((?!hede).)*$
前者检查<!>#8220; hede <!>#8221;只在输入字符串<!>#8217;的第一个位置,而不是在每个位置。
如果你只是将它用于grep,你可以使用grep -v hede
获取所有不包含hede的行。
ETA哦,重读这个问题,grep -v
可能是你的意思<!>“工具选项<!>”;
<强>答案:强>
^((?!hede).)*$
<强>解释强>
^
字符串的开头,
(
分组并捕获到\ 1(0次或更多次(匹配尽可能多的数量)),
(?!
向前看,看看是否有,
hede
你的字符串,
)
结束前瞻,
.
除\ n,
之外的任何字符
)*
结束\ 1(注意:因为您在此捕获中使用量词,所以只有最后重复捕获的模式将存储在\ 1)
$
在可选的\ n之前,以及字符串的结尾
给出的答案非常好,只是一个学术观点:
理论计算机科学意义上的正则表达不可能这样做。对他们来说,它必须看起来像这样:
^([^h].*$)|(h([^e].*$|$))|(he([^h].*$|$))|(heh([^e].*$|$))|(hehe.+$)
这只是完全匹配。为子匹配做这件事甚至会更加尴尬。
如果整个字符串匹配,您希望正则表达式测试仅失败,则以下内容将起作用:
^(?!hede$).*
e.g。 - 如果要允许除<!>“foo <!>”之外的所有值; (即<!>“foofoo <!>”,<!>“barfoo <!>”;和<!>“; foobar <!>”将传递,但<!>“foo <! >“将失败”,使用:^(?!foo$).*
当然,如果您正在检查完全相等,那么在这种情况下更好的通用解决方案是检查字符串是否相等,即
myStr !== 'foo'
如果您需要任何正则表达式功能(此处为不区分大小写和范围匹配),您甚至可以将否定置于测试之外:
!/^[a-f]oo$/i.test(myStr)
此答案顶部的正则表达式解决方案可能会有所帮助,但是,在需要正面的正则表达式测试的情况下(可能是通过API)。
FWIW,由于常规语言(也称为理性语言)在互补下被关闭,因此总是可以找到否定另一个表达式的正则表达式(也称为理性表达式)。但实现这一目标的工具并不多。
Vcsn 支持此运算符(表示{c}
,后缀) )。
首先定义表达式的类型:标签是字母(lal_char
),例如从a
选择到z
(当使用补充时定义字母表当然非常重要),以及<!>“;值<!>”;为每个单词计算的只是一个布尔值: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.)))).*
。
您可以在那里。
这里有一个很好的解释,说明为什么否定任意正则表达式并不容易。我不得不同意其他答案:如果这不是一个假设的问题,那么正则表达式不是正确的选择。
使用负向前瞻,正则表达式可以匹配不包含特定模式的内容。 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()
);
基准
我决定评估一些呈现的选项并比较它们的性能,以及使用一些新功能。 .NET Regex引擎的基准测试: 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: ^(?>[^R]+|R(?!egex Hero))*$ 6.137 // Lookahead only on the right first letter
04: ^(?>(?:.*?Regex Hero)?)^.*$ 7.426 // Match the word and check if you're still at linestart
05: ^(?(?=.*?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可以扩展为
^(?>[^R]+|R+(?!egex Hero))*$
导致性能提升很小。
但总体上最具可读性和性能最快的解决方案似乎是05使用条件语句 或04与积极的量词。我认为Perl解决方案应该更快,更容易阅读。
不是正则表达式,但我发现使用带管道的串行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)
有了这个,你可以避免在每个位置测试前瞻:
/^(?:[^h]+|h++(?!ede))*+$/
相当于(for .net):
^(?>(?:[^h]+|h+(?!ede))*)$
旧答案:
/^(?>[^h]+|h+(?!ede))*$/
前面提到的(?:(?!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
我是这样做的:
^[^h]*(h(?!ede)[^h]*)*$
比其他答案更准确,更有效率。它实现了Friedl的 <!>“unrolling-the-loop <!>”效率技术,并且需要更少的回溯。
如果你想匹配一个字符来否定一个类似于否定字符类的单词:
例如,字符串:
<?
$str="aaa bbb4 aaa bbb7";
?>
不要使用:
<?
preg_match('/aaa[^bbb]+?bbb7/s', $str, $matches);
?>
使用:
<?
preg_match('/aaa(?:(?!bbb).)+?bbb7/s', $str, $matches);
?>
注意"(?!bbb)."
既不是后视也不是前瞻,它看起来像是当前的,例如:
"(?=abc)abcde", "(?!abc)abcde"
OP未指定或 标记 帖子以指示将在其中使用正则表达式的上下文(编程语言,编辑器,工具)。
对我来说,我有时需要在使用 Textpad
编辑文件时执行此操作。
hede
支持一些正则表达式,但不支持前瞻或后瞻,因此需要几个步骤。
如果我希望保留 不要 包含字符串 <=> 的所有行,我会这样做:
1。搜索/替换整个文件以添加唯一的<!>“标记<!>”;到包含任何文本的每一行的开头。
Search string:^(.)
Replace string:<@#-unique-#@>\1
Replace-all
2。删除包含字符串 <=> 的所有行(替换字符串为空):
Search string:<@#-unique-#@>.*hede.*\n
Replace string:<nothing>
Replace-all
3。此时,所有剩余的行 不要 包含字符串 <=> 。删除唯一的<!>“;标记<!>”;从所有行(替换字符串为空):
Search string:<@#-unique-#@>
Replace string:<nothing>
Replace-all
现在您拥有原始文本,其中包含删除字符串 <=> 的所有行。 结果
如果我期待 做其他事 只到 不 包含字符串 <=> ,我会这样做:
1。搜索/替换整个文件以添加唯一的<!>“标记<!>”;到包含任何文本的每一行的开头。
Search string:<@#-unique-#@>(.*hede)
Replace string:\1
Replace-all
2。对于包含字符串 <=> 的所有行,请删除唯一的<!>“;标记<!>”:
<*>
3。此时,所有以唯一<!>“标记<!>”, 不要 开头的行包含字符串 <=> 。我现在可以将 Something Else 仅用于那些行。
4。当我完成后,我删除了唯一的<!>“;标记<!>”;从所有行(替换字符串为空):
<*>
在我的观点中,最佳答案的更易读的变体:
^(?!.*hede)
基本上,<!>匹配在行的开头,当且仅当它没有'hede'时<!> quot; - 所以要求几乎直接转换为正则表达式。
当然,可能有多种故障要求:
^(?!.*(hede|hodo|hada))
详细信息: ^ anchor确保正则表达式引擎不会在字符串中的每个位置重试匹配,这将匹配每个字符串。
开头的^锚表示行的开头。 grep工具一次匹配每一行,在使用多行字符串的上下文中,您可以使用<!>“m <!>”;标志:
/^(?!.*hede)/m # JavaScript syntax
或
(?m)^(?!.*hede) # Inline flag
通过PCRE词 (*SKIP)(*F)
^hede$(*SKIP)(*F)|^.*$
这将完全跳过的行其中包含的确切串 hede
和相匹配的所有剩余线。
执行部分:
让我们考虑上述regex经分裂为两部分。
部分之前
|
符号。一部分 不应该是匹配的.^hede$(*SKIP)(*F)
部分后
|
符号。一部分 应该是匹配的.^.*$
第1部分
Regex引擎,将开始执行的一部分。
^hede$(*SKIP)(*F)
说明:
^
声称,我们正在开始。hede
相匹配的字符串hede
$
声称我们是在线结束。
因此其中包含的字符串 hede
会是匹配的。一旦regex引擎看到下面 (*SKIP)(*F)
(注:你可以写 (*F)
作为 (*FAIL)
)动词,它跳过和使匹配失败。 |
称为篡改或逻辑或操作者添加下PCRE动词哪些合格实习匹配的所有边界之间存在的每个字上的所有线路,除了发行中包含的确切串 hede
.看到演示的 在这里,.就是说,它试图匹配的字符从剩余串。现在regex在第二部分将被执行。
第2部分
^.*$
说明:
由于没有其他人直接回答被问及的问题,我会这样做。
答案是,使用POSIX grep
,字面上无法满足此要求:
grep "Regex for doesn't contain hede" Input
原因是POSIX \|
只需要使用基本正则表达式,它们不足以完成该任务(由于缺少交替和分组,它们无法解析常规语言)。
但是,GNU \(
实现了允许它的扩展。特别是,\)
是GNU实现BRE的交替运算符,egrep
和testinput.txt
是分组运算符。如果你的正则表达式引擎支持交替,负括号表达式,分组和Kleene星,并且能够锚定到字符串的开头和结尾,那么这就是这种方法所需要的。
使用GNU hede
,它将类似于:
grep "^\([^h]\|h\(h\|eh\|edh\)*\([^eh]\|e[^dh]\|ed[^eh]\)\)*\(\|h\(h\|eh\|edh\)*\(\|e\|ed\)\)$" Input
(在 Grail中找到) 以及手工制作的一些进一步优化。
您还可以使用一种工具来实现扩展正则表达式,比如<=>,去掉反斜杠:
egrep "^([^h]|h(h|eh|edh)*([^eh]|e[^dh]|ed[^eh]))*(|h(h|eh|edh)*(|e|ed))$" Input
这是一个测试它的脚本(注意它在当前目录中生成一个文件<=>):
#!/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
更新:我最近发现了Kendall Hopkins的优秀 FormalTheory 图书馆,用PHP编写,提供类似于Grail的功能。使用它,以及我自己编写的简化程序,我已经能够编写一个带有输入短语的负正则表达式的在线生成器(目前只支持字母数字和空格字符): http://www.formauri.es/personal/pgimeno/misc/non-match-regex/
对于<=>输出:
^([^h]|h(h|e(h|dh))*([^eh]|e([^dh]|d[^eh])))*(h(h|e(h|dh))*(ed?)?)?$
等同于上述。
代码中的两个正则表达式可能更易于维护,一个用于执行第一个匹配,然后如果匹配则运行第二个正则表达式以检查您希望阻止的异常情况,例如^.*(hede).*
然后具有适当的逻辑你的代码。
好的,我承认这不是对发布的问题的真正答案,它也可能比单个正则表达式使用稍多的处理。但对于那些来到这里寻找快速紧急修复异常情况的开发人员来说,这个解决方案不应该被忽视。
TXR语言支持正则表达式否定。
$ txr -c '@(repeat)
@{nothede /~hede/}
@(do (put-line nothede))
@(end)' Input
一个更复杂的示例:匹配以a
开头并以z
结尾的所有行,但不包含子字符串hede
:
$ 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
正则表达式否定本身并不是特别有用,但是当你也有交集时,事情会变得有趣,因为你有一套完整的布尔集操作:你可以表达<!>“匹配它的集合,除了符合<!>“;
的东西以下功能可帮助您获得所需的输出
<?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;
}
?>
如何使用PCRE的回溯控制动词来匹配不包含单词的行
这是我以前没见过的方法:
/.*hede(*COMMIT)^|/
如何运作
首先,它试图找到<!>“; hede <!>”;在某个地方。如果成功,此时(*COMMIT)
告诉引擎,不仅在发生故障时不回溯,而且在这种情况下也不会尝试进一步匹配。然后,我们尝试匹配一些不可能匹配的东西(在这种情况下,^
)。
如果一行不包含<!>“hede <!>”;然后第二个替代方案,一个空子模式,成功匹配主题字符串。
这种方法并不比负面前瞻更有效,但我想我只是把它扔在这里以防有人发现它很漂亮并且发现其用于其他更有趣的应用程序。
也许你会发现这个在谷歌的话,尝试编写一regex,能够比赛段的路线(而不是整个行),其做 不 包含一个子串。图克我一段时间来弄清楚,所以我要分享:
鉴于a string:
<span class="good">bar</span><span class="bad">foo</span><span class="ugly">baz</span>
我想要匹配 <span>
标签,这不包含substring"坏"。
/<span(?:(?!bad).)*?>
将匹配 <span class=\"good\">
和 <span class=\"ugly\">
.
注意到有两个集(层)圆括号:
- 最里面的一个是负面的预期(它不是一个捕获小组)
- 最外层的解释是通过红宝石作为捕获的集团,但我们不想这是一个捕获的集团,因此我加入?:在它开始,它不再是解释为捕获小组。
在红宝石:
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\">"]
更简单的解决方案是使用not运算符!
if 语句需要匹配<!> quot; contains <!> quot;并且不匹配<!>“;排除<!>”;
var contains = /abc/;
var excludes =/hede/;
if(string.match(contains) && !(string.match(excludes))){ //proceed...
我相信RegEx的设计师预计会使用非运营商。
使用 ConyEdit ,您可以使用命令行cc.gl !/hede/
获取不包含正则表达式的行匹配,或使用命令行cc.dl /hede/
删除包含正则表达式匹配的行。他们有相同的结果。
^((?!hede)。)* $是一个优雅的解决方案,除非它消耗字符,否则你将无法将其与其他标准相结合。例如,假设您想要检查<!>“hede <!>”是否存在;哈哈。<!>的存在这个解决方案可以工作,因为它不会消耗字符:
^(?!。 \ bhede \ b)(?=。 \ bhaha \ b)