正则表达式的进阶用法
本文将介绍几种正则表达式的进阶用法。
前瞻、后顾、负前瞻、负后顾(positive/negative lookahead/lookbehind)
当我们想要匹配一段“后面为特定内容”的文本,但又不想匹配“特定内容”时,可以使用前瞻(positive lookahead)。比如:
1 | |
只会匹配后面是 bar 的 foo:
1 | |
当我们想要匹配一段“后面不为特定内容”的文本时,可以使用负前瞻(negative lookahead)。比如:
1 | |
只会匹配后面不是 bar 的 foo:
1 | |
与前瞻、负前瞻相对应的是后顾、负后顾(positive lookbehind/negative lookbehind)。它们分别匹配“前面为特定内容”和“前面不为特定内容”的文本。比如:
1 | |
只会匹配前面是 bar 的 foo:
1 | |
而:
1 | |
只会匹配前面不是 bar 的 foo:
1 | |
原子组(atomic group)
原子组(atomic group)和普通组(group)类似,唯一的区别是,一旦原子组中的一个分支匹配成功,它就不允许回溯尝试其他分支。比如,普通组:
1 | |
可以匹配:
1 | |
而原子组:
1 | |
只会匹配:
1 | |
考虑文本为 foobar 时的情况,正则表达式引擎会挨个尝试组内的分支,首先是 foobar 分支,但这样就没有剩余文本匹配最后的 bar 了。对于普通组,引擎可以回溯尝试 foo 分支,最终匹配成功;但对于原子组,引擎不能回溯,最终匹配失败。
为什么要有原子组呢?
举个例子,用 (a+)+ 作为表达式,在匹配 aaaaaaaaaa! 的输入时,虽然最终会失败,正则表达式引擎还是会反复尝试不同组合,导致匹配时间急剧增长、CPU 占用升高、程序卡顿。这种情况通常称为“灾难性回溯”。原子组可以通过减少不必要的回溯,来避免这种情况。
非捕获组(non-capturing group)
对于普通组,除了包含多个分支外,正则表达式引擎还会“记住”匹配到的内容。在用 sed 等工具替换时,可以用 \1、\2 等引用匹配内容。如果只需要多个分支、不需要“记住”匹配内容,可以使用非捕获组(non-capturing group)。比如:
1 | |
总结
| 正(Positive) | 负(Negative) | |
|---|---|---|
| 前瞻(Lookahead) | (?=...) |
(?!...) |
| 后顾(Lookbehind) | (?<=...) |
(?<!...) |
| 语法 | |
|---|---|
| 原子组(Atomic group) | (?>...) |
| 非捕获组(Non-capturing group) | (?:...) |
在线工具
最后推荐一个非常方便的在线工具:https://regex101.com/。