一、正则表达式是什么?
正则表达式(Regular Expression,简称 regex 或 regexp)是一种用于匹配字符串中字符组合的模式。对于nlp方向,这是语言处理的基本工具之一,无论是验证输入格式、批量搜索替换、还是从日志里提取关键信息,正则都能用一行模式替代十几行字符串处理代码或者数据。
正则的核心思想是用特殊符号代表一类字符,再通过位点、重复次数和分组逻辑来描述目标字符串的结构。它的学习曲线比较陡峭,但一旦掌握,效率提升非常明显。
我的学习路线:先记住
* + ? ^ $这五个最常用的符号,再逐步理解()捕获组、[]和{}的约束作用,最后攻克四个断言(?=,?!,?<=,?<!)和(?:)非捕获组。建议配合在线正则测试工具(如 regex101.com)边写边试,理解得更快。
二、核心符号速记(* + ? ^ $)
正则表达式有五个最基础的符号:
| 符号 | 名称 | 含义 | 示例 |
|---|---|---|---|
* | 星号 | 匹配前面的子表达式 0 次或多次 | zo* 匹配 z、zo、zoo |
+ | 加号 | 匹配前面的子表达式 1 次或多次 | zo+ 匹配 zo、zoo,不匹配 z |
? | 问号 | 匹配前面的子表达式 0 次或 1 次 | do(es)? 匹配 do 或 does |
^ | 脱字符 | 匹配字符串的 开始位置 | ^Hello 匹配以 Hello 开头的行 |
$ | 美元符 | 匹配字符串的 结束位置 | world$ 匹配以 world 结尾的行 |
记忆技巧:
*可以为零,就像“星星可有可无”;+至少有一个,就像“加号增加”;?表示疑问“有没有都行”。
三、量词与范围约束({} 和 [])
1. 精确次数约束 {}
用花括号可以指定重复的精确次数或范围:
| 语法 | 含义 |
|---|---|
{n} | 恰好匹配 n 次 |
{n,} | 至少匹配 n 次 |
{n,m} | 匹配 n 到 m 次(n <= m) |
示例:
o{2}只能匹配food中的两个o,不能匹配Bob中的o。o{2,}匹配foooood中的所有o。o{1,3}匹配fooooood中的前三个o。
贪婪与非贪婪:默认情况下,*、+、? 和 {} 都是“贪婪”的——会尽可能多地匹配字符。在后面加一个 ? 可以变成“非贪婪”模式,尽可能少地匹配。
例如,字符串 "oooo":
o+匹配整个"oooo"o+?只匹配单个"o"
2. 字符集合 []
方括号定义一个字符集合,匹配其中任意一个字符:
| 语法 | 含义 |
|---|---|
[abc] | 匹配 a、b 或 c 中的任意一个 |
[^abc] | 匹配除 a、b、c 之外的任意字符(排除型) |
[a-z] | 匹配任意小写字母(范围) |
[A-Za-z0-9_] | 匹配字母、数字、下划线(等同于 \w) |
注意事项:
- 在
[]内部,大多数特殊字符(如.、*、+)会失去特殊含义,变成普通字符。只有\、^(在开头时)、-(在中间时)和]需要转义或特殊处理。 - 连字符
-如果出现在首位或末尾,就表示普通字符而非范围。例如[-az]匹配-、a或z。
四、分组与捕获
1. 捕获组 (pattern)
用圆括号包裹的表达式会被视为一个子表达式(子模式),匹配到的文本会被存储起来,供后续引用(反向引用)或提取。
示例:
(\d{4})-(\d{2})-(\d{2})
匹配日期 2025-04-16,三个捕获组分别对应年、月、日。
反向引用可以用 \1、\2 等来引用前面的捕获组内容。例如匹配重复单词:
\b(\w+)\s+\1\b
可以匹配 hello hello。
2. 非捕获组 (?:pattern)
有时候我们只想用括号进行分组(例如配合 | 做多选),但并不需要捕获匹配的文本。这时可以用 (?:pattern),它不会占用捕获组的编号,性能更好,语义也更清晰。
示例:
industr(?:y|ies)
这个表达式比 industry|industries 更简洁,而且不会产生多余的捕获组。
我通常在两种情况下使用非捕获组:一是只需要分组作用,二是希望提高正则执行效率(避免不必要的内存存储)。
五、四个断言(环视)
断言(Assertion)不匹配具体的字符,而是匹配位置(例如字符之前或之后必须满足某种条件)。它们不消耗字符,所以也叫“零宽断言”。
| 断言 | 名称 | 含义 | 示例 |
|---|---|---|---|
(?=pattern) | 正向先行断言 | 匹配后面是 pattern 的位置 | Windows(?=95|98) 能匹配 Windows98 中的 Windows,但不匹配 Windows3.1 |
(?!pattern) | 负向先行断言 | 匹配后面不是 pattern 的位置 | Windows(?!95|98) 能匹配 Windows3.1 中的 Windows,但不匹配 Windows98 |
(?<=pattern) | 正向后行断言 | 匹配前面是 pattern 的位置 | (?<=95|98)Windows 能匹配 98Windows 中的 Windows |
(?<!pattern) | 负向后行断言 | 匹配前面不是 pattern 的位置 | (?<!95|98)Windows 能匹配 3.1Windows 中的 Windows |
实际应用场景:
- 匹配不包含某些单词的字符串:
^(?!.*badword).*$ - 提取所有
$符号后的数字:(?<=\$)\d+ - 匹配
src=后面的 URL 但不包含引号:(?<=src=["'])[^"']+
六、常用元字符速查
| 字符 | 描述 |
|---|---|
. | 匹配除换行符 \r、\n 之外的任意单个字符 |
\d | 匹配任意数字,等价于 [0-9] |
\D | 匹配任意非数字,等价于 [^0-9] |
\w | 匹配单词字符(字母、数字、下划线),等价于 [A-Za-z0-9_] |
\W | 匹配非单词字符 |
\s | 匹配空白字符(空格、制表符、换页符等),等价于 [ \f\n\r\t\v] |
\S | 匹配非空白字符 |
\b | 匹配单词边界(单词开始或结束的位置) |
\B | 匹配非单词边界 |
\n | 换行符 |
\r | 回车符 |
\t | 制表符 |
\f | 换页符 |
\v | 垂直制表符 |
注意:在 Unicode 环境下,\d、\w、\s 等可能匹配全角字符,如果需要严格 ASCII 范围,使用 [0-9]、[A-Za-z0-9_] 等自定义字符类。
七、运算符优先级
正则表达式中的元字符和结构也有优先级,从高到低排列如下:
| 优先级 | 符号 | 说明 |
|---|---|---|
| 最高 | \ | 转义符 |
| 高 | ()、(?:)、(?=)、[] | 分组或字符类 |
| 中 | *、+、?、{n}、{n,}、{n,m} | 量词 |
| 低 | ^、$、\b、\B | 锚点/位置 |
| 次最低 | 串接 | 相邻字符连接(如 ab) |
| 最低 | | | 交替(或) |
例子:^ab|cd$ 的实际含义是 (^ab)|(cd$),而不是 ^(ab|cd)$。如果需要匹配整行是 ab 或 cd,应该使用 ^(ab|cd)$。
八、实战案例
1. 验证邮箱地址(基础版)
^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$
2. 提取 HTML 标签内的文本(非贪婪)
<title>(.*?)</title>
3. 匹配 IPv4 地址
\b(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\b
4. 匹配 24 小时制时间(HH:MM)
^(?:[01]\d|2[0-3]):[0-5]\d$
5. 从日志中提取所有错误代码(假设格式为 ERROR [code:1234])
(?<=ERROR \[code:)\d+(?=\])
九、常见陷阱与建议
- 转义字符:在正则表达式中,有 12 个字符需要转义才能表示其字面意义:
^ $ ( ) * + ? . [ { \ |。例如要匹配(a+b),需要写成\(a\+b\)。 - 换行符:默认情况下
.不匹配换行符。如果需要跨行匹配,使用[\s\S]或开启s模式(单行模式)。 - 性能:复杂的正则尤其是嵌套量词可能导致“灾难性回溯”。尽量使用非捕获组、避免不必要的嵌套,必要时使用原子组
(?>pattern)(部分引擎支持)。 - 测试工具:推荐 regex101.com(支持多种语言风格,提供详细解释)或 Regexr。我习惯在写任何稍复杂的正则前,先在测试工具里验证边界情况。
十、速查表(正则常用语法)
| 需求 | 表达式 |
|---|---|
| 数字 | \d 或 [0-9] |
| 非数字 | \D |
| 字母或数字或下划线 | \w |
| 空白字符 | \s |
| 单词边界 | \b |
| 开始位置 | ^ |
| 结束位置 | $ |
| 0 或多次 | * |
| 1 或多次 | + |
| 0 或 1 次 | ? |
| 精确 n 次 | {n} |
| n 到 m 次 | {n,m} |
| 捕获组 | (...) |
| 非捕获组 | (?:...) |
| 正向先行断言 | (?=...) |
| 负向先行断言 | (?!...) |
| 正向后行断言 | (?<=...) |
| 负向后行断言 | (?<!...) |
| 逻辑或 | | |