一、正则表达式是什么?

正则表达式(Regular Expression,简称 regex 或 regexp)是一种用于匹配字符串中字符组合的模式。对于nlp方向,这是语言处理的基本工具之一,无论是验证输入格式、批量搜索替换、还是从日志里提取关键信息,正则都能用一行模式替代十几行字符串处理代码或者数据。

正则的核心思想是用特殊符号代表一类字符,再通过位点、重复次数和分组逻辑来描述目标字符串的结构。它的学习曲线比较陡峭,但一旦掌握,效率提升非常明显。

我的学习路线:先记住 * + ? ^ $ 这五个最常用的符号,再逐步理解 () 捕获组、[]{} 的约束作用,最后攻克四个断言(?=, ?!, ?<=, ?<!)和 (?:) 非捕获组。建议配合在线正则测试工具(如 regex101.com)边写边试,理解得更快。

二、核心符号速记(* + ? ^ $

正则表达式有五个最基础的符号:

符号名称含义示例
*星号匹配前面的子表达式 0 次或多次zo* 匹配 zzozoo
+加号匹配前面的子表达式 1 次或多次zo+ 匹配 zozoo,不匹配 z
?问号匹配前面的子表达式 0 次或 1 次do(es)? 匹配 dodoes
^脱字符匹配字符串的 开始位置^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]匹配 abc 中的任意一个
[^abc]匹配除 abc 之外的任意字符(排除型)
[a-z]匹配任意小写字母(范围)
[A-Za-z0-9_]匹配字母、数字、下划线(等同于 \w

注意事项

  • [] 内部,大多数特殊字符(如 .*+)会失去特殊含义,变成普通字符。只有 \^(在开头时)、-(在中间时)和 ] 需要转义或特殊处理。
  • 连字符 - 如果出现在首位或末尾,就表示普通字符而非范围。例如 [-az] 匹配 -az

四、分组与捕获

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)$。如果需要匹配整行是 abcd,应该使用 ^(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+(?=\])

九、常见陷阱与建议

  1. 转义字符:在正则表达式中,有 12 个字符需要转义才能表示其字面意义:^ $ ( ) * + ? . [ { \ |。例如要匹配 (a+b),需要写成 \(a\+b\)
  2. 换行符:默认情况下 . 不匹配换行符。如果需要跨行匹配,使用 [\s\S] 或开启 s 模式(单行模式)。
  3. 性能:复杂的正则尤其是嵌套量词可能导致“灾难性回溯”。尽量使用非捕获组、避免不必要的嵌套,必要时使用原子组 (?>pattern)(部分引擎支持)。
  4. 测试工具:推荐 regex101.com(支持多种语言风格,提供详细解释)或 Regexr。我习惯在写任何稍复杂的正则前,先在测试工具里验证边界情况。

十、速查表(正则常用语法)

需求表达式
数字\d[0-9]
非数字\D
字母或数字或下划线\w
空白字符\s
单词边界\b
开始位置^
结束位置$
0 或多次*
1 或多次+
0 或 1 次?
精确 n 次{n}
n 到 m 次{n,m}
捕获组(...)
非捕获组(?:...)
正向先行断言(?=...)
负向先行断言(?!...)
正向后行断言(?<=...)
负向后行断言(?<!...)
逻辑或|

作者

884705373@qq.com

相关文章

QLoRA微调原理详解:与LoRA的性能与内存对比

引言:为什么大模型微调需要QLoRA? 在深...

读出全部

关于Norm的解析

可以说,如果没有残差连接和 Layer No...

读出全部

从 SGD 到 AdamW 的优化器

写在前面 在上一篇文章中,我们讨论了如何用数...

读出全部