我知道可以使用其他工具(例如 grep -v
)来匹配一个词并反转匹配结果。然而,是否可能使用正则表达式来匹配不包含特定单词(例如 hede
)的行?
输入:
hoho
hihi
haha
hede
代码:
grep "<Regex for 'doesn't contain hede'>" input
期望的输出:
hoho
hihi
haha
我知道可以使用其他工具(例如 grep -v
)来匹配一个词并反转匹配结果。然而,是否可能使用正则表达式来匹配不包含特定单词(例如 hede
)的行?
hoho
hihi
haha
hede
grep "<Regex for 'doesn't contain hede'>" input
hoho
hihi
haha
通过 PCRE 命令 (*SKIP)(*F)
^hede$(*SKIP)(*F)|^.*$
这将完全跳过包含字符串hede
的行,并匹配其余所有行。
部分执行:
我们将上述正则表达式分成两个部分。
|
符号之前的部分。该部分不应被匹配。
^hede$(*SKIP)(*F)
|
符号后的部分。这部分应该被匹配。
^.*$
第一部分
正则表达式引擎将从第一部分开始执行。
^hede$(*SKIP)(*F)
说明:
^
断言我们处于开头。hede
匹配字符串 hede
$
断言我们处于行尾。因此,包含字符串 hede
的行将被匹配。一旦正则表达式引擎看到以下的 (*SKIP)(*F)
(注意:您可以将 (*F)
写成 (*FAIL)
)语句,它将跳过并使匹配失败。在 PCRE(Perl Compatible Regular Expressions)相关语句后添加逻辑或运算符 |
,这将匹配除包含精确字符串 hede
的行之外的所有行中每个字符之间的所有边界。请参见演示此处。也就是说,它尝试从剩余的字符串中匹配字符。然后执行第二部分的正则表达式。
第二部分
^.*$
Explanation:
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
正则表达式的否定本身并不特别有用,但当您还拥有交集时,事情变得有趣了,因为您拥有完整的布尔集合操作:您可以表达“与此匹配的集合,除了与那个匹配的东西”。
^.*(hede).*
,然后在代码中使用适当的逻辑。^(?=.*?tasty-treats)((?!chocolate).)*$
(全局匹配、多行匹配)
交互式示例:https://regexr.com/53gv4
(这些网址包含“tasty-treats”,但不包含“chocolate”)
(这些网址中某处包含“chocolate”,因此它们不会匹配,即使它们包含“tasty-treats”)
<?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;
}
?>
Mark the negative match: (e.g. lines with hede
), using a character not included in the whole text at all. An emoji could probably be a good choice for this purpose.
s/(.*hede)/\1/g
Target the rest (the unmarked strings: e.g. lines without hede
). Suppose you want to keep only the target and delete the rest (as you want):
s/^.*//g
假设您想要删除目标:
Mark the negative match: (e.g. lines with hede
), using a character not included in the whole text at all. An emoji could probably be a good choice for this purpose.
s/(.*hede)/\1/g
Target the rest (the unmarked strings: e.g. lines without hede
). Suppose you want to delete the target:
s/^[^].*//g
Remove the mark:
s///g
^((?!hede).)*$
是一种优雅的解决方案,但由于它会消耗字符,您将无法与其他条件组合使用。例如,假设您想检查“hede”不存在且“haha”存在,这个解决方案可以工作,因为它不会消耗字符:
^(?!.*\bhede\b)(?=.*\bhaha\b)
这里介绍一种我以前没有见过的方法:
/.*hede(*COMMIT)^|/
首先,它尝试在行中找到"hede"。如果成功,此时(*COMMIT)
告诉引擎,在失败的情况下不仅不回溯,而且也不会尝试任何进一步的匹配。然后,我们尝试匹配一个不可能匹配的东西(在本例中,是^
)。
如果一行不包含"hede",则第二个选择,一个空的子模式,可以成功匹配主题字符串。
这种方法并不比负向先行断言更有效率,但我认为我应该将其放在这里,以防有人发现它很棒,并且能够在其他更有趣的应用程序中使用它。
一个更简单的解决方案是使用非运算符!
你的if语句需要匹配"包含"而不是匹配"排除"。
var contains = /abc/;
var excludes =/hede/;
if(string.match(contains) && !(string.match(excludes))){ //proceed...
我相信正则表达式的设计者们预料到了非运算符的使用。
<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\">
。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\">"]
([^h]*(h([^e]|$)|he([^d]|$)|hed([^e]|$)))*
有什么问题吗?它的思想很简单。继续匹配直到看到不想要的字符串的开头,然后只在字符串未完成的N-1种情况下进行匹配(其中N为字符串的长度)。这N-1种情况是“h之后非e”,“he之后非d”和“hed之后非e”。如果你成功通过了这些N-1种情况,那么你就成功地没有匹配上不想要的字符串,所以你可以开始再次寻找[^h]*
。 - stevendesu^([^h]*(h([^e]|$)|he([^d]|$)|hed([^e]|$))?)*$
这个模式在 "hhede" 等包含部分"hede"的实例之前出现时失败了。 - jaytea