Skip to main content

正则(regex)

量词(Quantifiers)

在正则表达式中,*+ 都是量词(Quantifiers),它们决定了前面的字符或分组可以重复出现的次数。

它们的核心区别在于:“是否允许出现 0 次”

符号名称匹配次数是否必选含义
*星号 (Asterisk)0 次或多次可选前面的内容可有可无,有的话可以有很多个。
+加号 (Plus)1 次或多次必选前面的内容至少要出现一次,也可以有很多个。

如果想限制具体的次数,可以使用大括号:

  • {n}:正好出现n次。
  • {n,}:至少出现n次。
  • {n,m}:出现n到m次。

此外,?跟在前面的字符后代表可有可无。

等价符号

.匹配除换行符(\n、\r)之外的任何单个字符。要匹配包括 ‘\n’ 在内的任何字符,请使用像"(.|\n)"的模式。
\d匹配一个数字字符。等价于 [0-9]。
\D匹配一个非数字字符。等价于 [ ^0-9]。
\w匹配一个字母、数字、下划线。等价于[A-Za-z0-9_]。
\W匹配非(字母、数字、下划线)。等价于 [ ^A-Za-z0-9_]。
\s匹配任何空白字符,包括空格、制表符、换页符等等。等价于 [ \f\n\r\t\v]。
\S匹配任何非空白字符。等价于 [ ^ \f\n\r\t\v]。
\n匹配一个换行符。等价于 \x0a 和 \cJ。
\r匹配一个回车符。等价于 \x0d 和 \cM。
\t匹配一个制表符。等价于 \x09 和 \cI。

贪婪匹配与懒惰匹配

简单来说,**贪婪匹配(Greedy)懒惰匹配(Lazy/Non-greedy)**的区别在于:面对多个可能的匹配结果时,是“能拿多少拿多少”还是“够了就行”。在正则表达式中,这两种模式直接影响你抓取数据的范围。

1. 贪婪匹配 (Greedy)

这是正则表达式的默认模式

  • 行为: 匹配器会尽可能多地吞掉字符,直到不符合条件为止。
  • 常用符号: *(0或多个)、+(1或多个)、{n,}
  • 例子: 假设我们要匹配 <div>内容1</div><div>内容2</div> 中的 HTML 标签。
    • 正则: <div>.*</div>
    • 匹配结果: <div>内容1</div><div>内容2</div>
    • 解析: 它没有在第一个 </div> 停止,而是直接匹配到了文档的最后一个 </div>

2. 懒惰匹配 (Lazy / Non-greedy)

  • 行为: 匹配器在满足条件的前提下,尽可能少地匹配字符。一旦找到匹配项,立即停止。
  • 实现方式: 在贪婪量词后面加一个问号 ?
  • 常用符号: *?+???{n,}?
  • 例子: 同样的 HTML 字符串。
    • 正则: <div>.*?</div>
    • 匹配结果: <div>内容1</div> (以及接下来的 <div>内容2</div>
    • 解析: 碰到第一个符合条件的 </div> 它就“收手”了。

核心区别对比

特性贪婪匹配 (Greedy)懒惰匹配 (Lazy)
性格贪心,全都要知足,够了就好
匹配长度匹配最长的字符串匹配最短的字符串
控制符*, +, {n,}*?, +?, {n,}?
回溯机制从后往前找(先吞下全部再吐出来)从前往后找(吃一点看一点)

举个直观的例子

字符串:"apple" "banana" "cherry"

  • 贪婪模式 ".*":匹配结果是整个字符串 "apple" "banana" "cherry"。它认为最开头的 " 和最末尾的 " 才是这一对。
  • 懒惰模式 ".*?":匹配结果是三个独立的项:"apple""banana""cherry"

特殊字符

**^:**匹配搜索字符串开始的位置。 如果标志中包括 m(多行搜索)字符,^ 还将匹配 \n 或 \r 后面的位置。

**$:**匹配搜索字符串结尾的位置。 如果标志中包括 m(多行搜索)字符,^ 还将匹配 \n 或 \r 前面的位置。

正则表达式 ^.{2,100}$ 匹配的是 任意长度在 2 到 100 个字符之间的字符串。

3. .*?http 与 .*http 的区别

这是理解贪婪和非贪婪匹配的直观例子。假设字符串是:

访问 http://first.example,然后继续访问 http://second.example
非贪婪:.*?http

.*? 会尽可能少地匹配字符。它从当前位置开始一点点扩展,直到后面的 http 第一次匹配成功:

正则:.*?http
结果:访问 http

第一个 http

匹配过程可以简化为:

.*?  先匹配 0 个字符
↓ 后面的 http 匹配失败
.*? 再多匹配一个字符
↓ 继续尝试 http
...
↓ 到达第一个 http
http 匹配成功,立即停止

因此,.*?http 通常用于“从当前位置开始,截取到第一个 http 为止”的场景。需要注意,它匹配到的整体结果包含前面的字符和 http,并不只是 http 本身;如果只想提取关键字,应直接使用 http

贪婪:.*http

.* 是贪婪量词,会先尽可能多地吞掉字符。它会先尝试匹配到字符串末尾,然后为了让后面的 http 成功,从末尾开始回溯,最终停在最后一个可以匹配的 http

正则:.*http
结果:访问 http://first.example,然后继续访问 http

最后一个 http

匹配过程可以简化为:

.*   先尽可能匹配到末尾
↓ 后面的 http 匹配失败
.* 向前回溯字符
↓ 继续尝试 http
...
↓ 回溯到最后一个 http
http 匹配成功,停止回溯

所以,在同一个字符串和同一个起始位置上,通常可以这样记忆:

正则量词行为http 命中的位置
.*?http非贪婪,尽快满足后续条件第一个 http
.*http贪婪,先尽量扩展再回溯最后一个 http

这里的“第一个”和“最后一个”是指在同一次匹配、同一个起始位置和同一个文本范围内的结果。正则引擎是否跨越换行,还取决于点号的行为以及是否启用了 DOTALL、s 等模式。

与捕获组结合

如果希望只提取目标标记,可以把目标放在捕获组中:

正则:.*?(http)
捕获组:第一个 http

如果要匹配第一个 URL,可以使用更明确的字符范围,而不是让点号任意跨越内容:

正则:https?://[^\s]+
结果:http://... 或 https://...

.*? 适合快速表达“到第一个目标标记为止”,但在 HTML、日志或嵌套结构中,最好根据实际分隔符使用更精确的字符类;正则表达式并不能完整解析任意嵌套结构。

表达式

括号表达式

绝大多数在外部具有特殊意义的元字符,一旦进入方括号,都会自动失去其特殊魔力,变成一个普通的字面量字符。但也有一些字符需要转义:

  • \ (反斜杠):它在任何地方几乎都需要转义,所以你会看到正则里写的是 \\
  • ^ (脱字符):只有当它放在 [第一位时,它才代表“取反”。如果它在中间(如 [a^b]),它就只代表普通字符。
  • - (连字符):在 [] 内部表示范围(如 a-z)。如果想匹配减号本身,得把它放在开头或结尾。
  • ] (闭括号):如果你想在集合里包含闭括号,通常需要转义 \],否则正则引擎会认为集合在这里结束了。

编程语言中的正则实现