正则表达式高阶使用:负向零宽度断言
date
Dec 31, 2021
slug
regex-lookaround
status
Published
tags
summary
type
Post
当我在写Surge的规则配置文件时,我发现我需要通过正则表达式使用 policy-regex-filter 进行过滤,具体的需求是:排除包含指定字符串的节点,过滤获得没有包含指定字符串的节点。
这是个难题,平时使用正则表达式主要是“匹配”的思想,这次要使用“否定排除”的思想。
零宽度断言 (zero-width-assertions) 解决了这个问题。 我们这里讨论的是否定的断言。 零宽度断言所匹配到的内容并不会保存到结果中去。
^((?!hede).)*$上面的正则表达式是用来匹配排除包含
hede的结果。
一个字符串包含 n 个字符的 list,在每个字符的前后都包涵一个空的字符串,因此含有 n 个字符的 list 拥有 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). 会检查每个的e的后面有没有 “hede” 子字符串,如果没有,. 会匹配任意一个字符(除了换行符),这种行为称为“零宽度断言”,因为他们没有消耗任何字符,仅仅只是在断言或证实某些东西。 因此,在上面的例子中,对于每个空字符串都会被验证其之后有无 “hede”,之后再与 . 匹配。 表达式 (?!hede). 只会进行一次上面的验证,因此,我们将其成组重复多次:((?!hede).)*。 最后,我们锚定首尾:^((?!hede).)*$。"ABhedeCD" 最终会在 e3 处失败,因为 e3 之后存在字符串 “hede”。我们再将其拓展一下,可以匹配排除包含多个指定字符串的节点:
^((?!(本站|流量|过期|下架|用户群|官网|精简)).)*$准确来说,上面使用的属于“正向否定查找”,常规用法是:
x(?=y),含义是仅仅当’x’后面不跟着’y’时匹配’x'。例如,仅仅当这个数字后面没有跟小数点的时候,
\d+(?!\.) 匹配一个数字。正则表达式 \d+(?!\.) 匹配‘141’而不是‘3.141’。但
(?!hede). 的这种用法属实少见,其思想是将其匹配字符之前的部分看作是一个字符串(空字符串)([空字符串](?!hede).),而检查是这个空字符串的后面是否匹配(?!hede)。