参考:
正则表达式的先行断言和后行断言一共有4种形式:
x(?=y):零宽正向先行断言(zero-width positive lookahead assertion)(向前断言):x被y跟随时匹配x,意思就是只有后面能匹配y时匹配x。x(?!y):零宽负向先行断言(zero-width negative lookahead assertion)(向前否定断言):x没有被y紧随时匹配x,意思就是只有后面不能匹配y时匹配x。(?<=y)x:零宽正向后行断言(zero-width positive lookbehind assertion)(向后断言):x跟随y时匹配x,意思是只有x前面的能够匹配y时才匹配x。(?<!y)x:零宽负向后行断言(zero-width negative lookbehind assertion)(向后否定断言):x不跟随y时匹配x,意思是只有x前面的不能匹配y时匹配x。
零宽断言正如它的名字一样,是一种零宽度的匹配,它匹配到的内容不会保存到匹配结果中去,最终匹配结果只是一个位置而已。
作用是给指定位置添加一个限定条件,用来规定此位置之前或者之后的字符必须满足限定条件才能使正则中的字表达式匹配成功。
对于这4个断言的理解,可以从两个方面入手:
- 关于先行(lookahead)和后行(lookbehind):正则表达式引擎在执行字符串和表达式匹配时,会从头到尾(从前到后)连续扫描字符串中的字符,设想有一个扫描指针指向字符边界处并随匹配过程移动。先行断言,是当扫描指针位于某处时,引擎会尝试匹配指针还未扫过的字符,先于指针到达该字符,故称为先行。后行断言,引擎会尝试匹配指针已扫过的字符,后于指针到达该字符,故称为后行。
- 关于正向(positive)和负向(negative):正向就表示匹配括号中的表达式,负向表示不匹配。
对这4个断言形式的记忆:
- 先行和后行:后行断言
(?<=y)x、(?<!y)x中,有个小于号,同时也是箭头,对于自左至右的文本方向,这个箭头是指向后的,这也比较符合我们的习惯。把小于号去掉,就是先行断言。 - 正向和负向:不等于
!=、逻辑非!都是用!号来表示,所以有!号的形式表示不匹配、负向;将!号换成=号,就表示匹配、正向。
上述4种断言,括号里的y本身是一个正则表达式。但对2种后行断言有所限制,在Perl和Python中,这个表达式必须是定长(fixed length)的,即不能使用*、+、?等元字符,如(?<=abc)没有问题,但(?<=a*bc)是不被支持的,特别是当表达式中含有|连接的分支时,各个分支的长度必须相同。之所以不支持变长表达式,是因为当引擎检查后行断言时,无法确定要回溯多少步。Java支持?、{m}、{n,m}等符号,但同样不支持*、+字符。Javascript干脆不支持后行断言,不过一般来说,这不是太大的问题(ES2018引入后行断言,V8引擎4.9版(Chrome 62)已经支持。)
其他边界类断言
| 字符 | 含义 |
|---|---|
^ | 匹配输入的开头。如果多行模式设为true,^在换行符后也能立即匹配。^出现在集合或范围开头时的含义与此不同。 |
$ | 匹配输入的结束。如果多行模式设为true,$在换行符前也能立即匹配。 |
\b | 匹配一个单词的边界,这是一个字的字符前后没有另一个字的字符位置,例如在字母和空格之间。需要注意的是匹配的单词边界不包括在匹配中。换句话说,匹配字边界的长度为零。 |
\B | 匹配非单词边界。这是上一个字符和下一个字符属于同一类型的位置:要么两者都必须是单词,要么两者都必须是非单词,例如在两个字母之间或两个空格之间。字符串的开头和结尾被视为非单词。与匹配的词边界相同,匹配的非词边界也不包含在匹配中。 |