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

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

技术背景

在处理文本时,有时需要匹配不包含特定单词的行。虽然正则表达式通常不擅长反向匹配,但可以通过负向预查(negative look - around)来模拟这种行为。

实现步骤

不包含特定字符串的正则

使用负向预查 (?!...) 可以实现匹配不包含特定字符串的功能。例如,要匹配不包含 hede 的字符串,可以使用以下正则表达式:

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

此正则会匹配任何不包含 hede 的字符串或行(无换行符)。如果需要匹配包含换行符的情况,可以使用 DOT - ALL 修饰符:

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

或者内联使用:

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

若不支持 DOT - ALL 修饰符,可使用字符类 [\s\S] 来模拟:

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

不包含特定字符串的优化正则

原正则在匹配单个字符时,负向预查部分会向前测试 1 到 4 个字符。可以让负向预查部分检查整个文本,确保没有 hede,然后正常部分 .* 一次性匹配整个文本,改进后的正则如下:

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

*? 是懒惰量词,也可以使用贪婪量词 *,具体取决于数据情况。

不包含特定字符串的其他实现方式

  • 使用 Vcsn:Vcsn 支持补运算,通过它可以找到否定另一个表达式的正则。在 Python 中使用示例:
1
2
3
4
5
import vcsn
c = vcsn.context('lal_char(a - z), b')
e = c.expression('(hede){c}')
a = e.automaton()
print(a.expression())
  • 使用 POSIX grep:POSIX grep 无标志时仅支持基本正则表达式(BREs),无法完成此任务。但 GNU grep 实现了扩展,允许使用。例如:
1
grep "^([^h]|h(h|eh|edh)*([^eh]|e[^dh]|ed[^eh]))*(|h(h|eh|edh)*(|e|ed))$" input

也可以使用 egrep 或给 POSIX grep 传递 -E 标志:

1
egrep "^([^h]|h(h|eh|edh)*([^eh]|e[^dh]|ed[^eh]))*(|h(h|eh|edh)*(|e|ed))$" input
  • 使用串行 grep:可以使用串行 grep 结合管道来消除噪声,例如搜索 Apache 配置文件中不包含注释且匹配 dir 的行:
1
grep -v '\#' /opt/lampp/etc/httpd.conf | grep -i dir
  • 使用 Textpad 编辑器:如果在 Textpad 中操作,由于其不支持预查,可以通过以下步骤保留不包含 hede 的行:
    1. 搜索替换添加唯一标签:搜索 ^(.),替换为 <@# - unique - #@>\1
    2. 删除包含 hede 的行:搜索 <@# - unique - #@>.*hede.*\n,替换为空。
    3. 移除唯一标签:搜索 <@# - unique - #@>,替换为空。
  • 使用 Ruby 的 Absent 运算符:自 ruby - 2.4.1 起,可以使用新的 Absent 运算符,例如 ^(?~hede)$ 可以匹配不包含 hede 的字符串:
1
["hoho", "hihi", "haha", "hede"].select{|s| /^(?~hede)$/.match(s)}
  • 使用 PCRE 动词 (*SKIP)(*F):正则 ^hede$(*SKIP)(*F)|^.*$ 可以完全跳过包含精确字符串 hede 的行,并匹配其余所有行。

核心代码

JavaScript 示例

1
2
3
4
5
6
// 匹配不包含 hede 的字符串
const regex = /^(?!.*?hede).*$/;
const str1 = "hello world";
const str2 = "hede is here";
console.log(regex.test(str1)); // true
console.log(regex.test(str2)); // false

PHP 示例

1
2
3
$str = "aaa        bbb4      aaa     bbb7";
preg_match('/aaa(?:(?!bbb).)+?bbb7/s', $str, $matches);
print_r($matches);

最佳实践

  • 性能方面:通过基准测试发现,^(?![.*?Regex Hero).* 这种简单负向预查的方式在性能和可读性上表现较好,尤其是在 JavaScript 中,因为 JS 不支持其他方案的高级正则特性。
  • 代码可读性:使用 ^(?![.*?hede) 这种形式将需求直接转化为正则,逻辑清晰,易于理解。

常见问题

正则性能问题

复杂的负向预查可能会导致性能下降,特别是在处理长文本时。可以通过优化正则表达式,如使用 ^(?![.*?hede) 这种先整体检查的方式,减少回溯来提高性能。

工具支持问题

不同的工具对正则表达式的支持不同。例如,POSIX grep 基本正则表达式不支持完成此任务所需的功能,而 GNU grep 则支持。在使用时需要根据具体工具的特性选择合适的正则表达式。

边界情况处理

在处理包含换行符、空行等边界情况时,需要注意正则表达式的编写。例如,使用 .* 而不是 .+ 可以匹配空行。


Regular expression to match a line that doesn't contain a word
https://119291.xyz/posts/2025-05-08.regular-expression-to-match-line-without-word/
作者
ww
发布于
2025年5月8日
许可协议