正则, 惰性匹配(lazy) .*?
http://www.nowamagic.net/librarys/veda/detail/1038
使用场景 有时,我们想用正则匹配以某个子串开头,且以某个子串或字符结尾的子字符串,但是结尾的字串或字符在原字符串中出现了多次,但我们只想匹配从开始处到第一次出现的地方,换句话说,想得到开始和结尾之间内容最少的匹配。
正则的贪婪与非贪婪(惰性) 通常使用如下字符类描述前导字符的重复特征:
- ?: 告诉引擎匹配前导字符0次或一次。事实上是表示前导字符是可选的。
- +: 告诉引擎匹配前导字符1次或多次。
- *: 告诉引擎匹配前导字符0次或多次。
- {min, max}: 告诉引擎匹配前导字符min次到max次。min和max都是非负整数。如果有逗号而max被省略了,则表示max没有限制;如果逗号和max都被省略了,则表示重复min次。
因此 {0,} 和 * 一样,{1,} 和 + 的作用一样。
贪婪 默认情况下,? + * {min, max}都是贪婪的,也就是说,它会根据前导字符去匹配尽可能多的内容。
非贪婪(惰性) 非贪婪就是匹配尽可能少的内容。
原理浅析
结合实例来分析哈基于正则的引擎对文本的匹配过程。原始字符串:This is a <EM>first</EM> test,使用正则 <.+> 来匹配HTML标签,期望第一次匹配得到<EM>,第二次匹配得到</EM>,实际却是第一次匹配就得到了 <EM>first</EM>。
来看看匹配过程,第一个记号是 <,这是一个文本字符,匹配其自身。第二个符号是.,匹配了字符E,然后+一直可以匹配其余的字符,直到一行的结束。然后到了换行符,匹配失败(.不匹配换行符)。于是引擎开始对下一个正则表达式符号进行匹配,即试图匹配 >。到目前为止,<.+ 已经匹配了 <EM>first</EM> test。引擎会试图将 > 与换行符进行匹配,结果失败了。于是引擎进行回溯。回溯后的匹配状况是 <.+ 匹配 <EM>first</EM> tes。于是引擎将 > 与 t 进行匹配。显然还是会失败。这个过程继续,直到 <.+ 匹配 <EM>first</EM,> 与 > 匹配。于是引擎找到了一个匹配 <EM>first</EM>。记住,正则导向的引擎是急切的,所以它会急着报告它找到的第一个匹配。而不是继续回溯,即使可能会有更好的匹配,例如<EM>。所以我们可以看到,由于+的贪婪性,使得正则表达式引擎返回了一个最左边的最长的匹配。
如果想得到期望的结果,就需要启用非贪婪模式:<.+?>
总结:如果是贪婪匹配模式,正则引擎会一直匹配到字符串最后;当匹配为false时,就回溯以找到倒数第一个匹配位置,返回匹配结果。 如果是非贪婪匹配模式,正则引擎会匹配到符合pattern的末尾位置那个字符,然后再往后走一步,发现匹配为false时,就回溯以找到最近一个匹配为true的位置,返回匹配结果。
实例 例如,原始字符串:
{“accesskey”:{“acccessKeyId”:“XhUURxsMlJE6EiXf”,“accessKeySecret”:“Q9fMpgBgRnKycMRD28MMkkFMbiNkbY”},“dbGrant”:{“0000031736”:“READWRITE”},“dbSchemaId”:“0000031737”} 现在想把这部分敏感信息替换为空字符串:
“accesskey”:{“acccessKeyId”:“XhUURxsMlJE6EiXf”,“accessKeySecret”:“Q9fMpgBgRnKycMRD28MMkkFMbiNkbY”}, 先不考虑结尾的逗号,尝试正则:“accesskey”:{.+},直接匹配至原始字符串结尾的}字符,因为引擎默认会匹配尽可能多的内容。
考虑到贪婪性,将正则修改为:“accesskey”:{.+}+?,匹配结果一样。纳尼?难道我对贪婪性的理解有问题。梳理哈使用姿势,我期望它匹配到开始位置之后出现的第一个}字符,对应的表达式部分为}+?。套用非贪婪模式分析问题,期望对一个或多个}字符进行匹配,且匹配尽可能少的内容,但在原始串中,}字符都是分开的,没有连续,无论如何只能匹配一个单独的}字符。可见对}字符开启非贪婪模式匹配行不通。
想要匹配到开始位置之后出现的第一个}字符 也可以表达为 开始位置和末尾}字符之间的内容最少,对应正则部分修改为:.+?,完整表达式:“accesskey”:{.+?},测试匹配结果,妥妥的。
https://segmentfault.com/a/1190000002640851
维护记录
| 时间 | 修改内容 | 原因 |
|---|---|---|
| 2026-10-09 | 修复本地 Hugo 构建的 Raw HTML 警告:代码/配置放入代码块,正文中的尖括号占位符改为行内代码;文件重命名为 regex-lazy-matching.md;title 改为「Regex Lazy Matching 正则惰性匹配 .*?」;categories 改为 CS |
正文中的 HTML/XML 片段被当作原始 HTML 丢弃;文件名/URL/标题按规范调整 |