正则表达式基础:元字符、字符类、量词、分组与捕获
好,咱们直接进入正题。正则表达式这东西,说白了就是一套描述文本模式的"密码"。你给计算机一套规则,它就能在成堆的数据里帮你找到想要的内容。我在做敏感数据识别的时候,正则就是第一道筛子——先把可能的手机号、身份证号这些候选人捞出来。
这一章,咱们把这套密码的四个核心零件拆开讲清楚。掌握了它们,你就能看懂大多数正则写法了。
本章思维导图
下面这张SVG图把四个核心概念串起来了。我建议你把它作为一个整体来看——每个部分都在实际项目中反复出现。
1. 元字符:正则的"标点符号"
元字符是什么?就是那些有特殊含义的符号。比如点号 .,它不代表"句号",而是代表"任意单个字符"。我刚开始学的时候,这一点就经常搞混。
| 元字符 | 含义 | 匹配示例 |
|---|---|---|
. |
匹配除换行符以外的任意单个字符 | a.b 可以匹配 acb、a3b |
^ |
匹配字符串的开头 | ^Hello 只匹配开头的 Hello |
$ |
匹配字符串的结尾 | world$ 只匹配结尾的 world |
\ |
转义符号,将元字符变成普通字符 | \. 匹配真正的点号 |
| |
逻辑或 | cat|dog 匹配 cat 或 dog |
() |
分组,稍后细讲 | (ab)+ 匹配 ababab |
个人经验:在验证电话号码时,我经常用 ^1[3-9]\d{9}$。这里的 ^ 和 $ 用来掐头去尾,确保整串都是号码,而不是某段文字里冒出来的11位数字。这个习惯帮我避免过不少误报。
这里有个关键点要记牢:如果你真的想匹配一个点号、星号这类特殊字符,记得在它前面加反斜杠 \ 转义。比如文件名里常见的 test\.txt。
2. 字符类:给匹配范围画个圈
字符类用方括号 [] 表示,用来指定"我想在这一位上匹配这些字符中的某一个"。这玩意儿在敏感数据识别里太常用了。
// 字符类示例
[abc] // 匹配 a、b、c 中的任意一个
[a-z] // 匹配任意小写字母
[0-9] // 匹配任意数字
[a-zA-Z0-9] // 匹配任意字母或数字
[^0-9] // 匹配除数字以外的任意字符(^放在开头表示取反)
你看看,身份证号里最后一位可能是数字也可能是X,那字符类就能写成 [0-9Xx]。我在写车牌号识别的时候也用了类似思路——省事又准确。
| 预定义字符类 | 等价写法 | 说明 |
|---|---|---|
\d |
[0-9] |
任意数字 |
\D |
[^0-9] |
任意非数字 |
\w |
[a-zA-Z0-9_] |
单词字符(字母、数字、下划线) |
\W |
[^a-zA-Z0-9_] |
非单词字符 |
\s |
[ \t\n\r\f] |
空白字符 |
\S |
[^ \t\n\r\f] |
非空白字符 |
注意坑位:字符类里面的 ^ 如果放在开头,表示"取反";如果放在中间或结尾,就是普通字符。我见过不少人把这个搞混,结果取反变成了匹配尖括号——哭笑不得。
举个例子:[^abc] 匹配不是 a、b、c 的字符;[a^bc] 匹配 a、^、b、c 这四个字符中的任意一个。
3. 量词:控制重复次数
量词决定前面的表达式要重复多少次。没有量词,正则就成了一对一的匹配——那等于没用。
| 量词 | 含义 | 示例 |
|---|---|---|
* |
0次或多次 | ab*c 匹配 ac、abc、abbc 等 |
+ |
1次或多次 | ab+c 匹配 abc、abbc,但不匹配 ac |
? |
0次或1次 | colou?r 匹配 color 和 colour |
{n} |
恰好n次 | \d{4} 匹配恰好4个数字 |
{n,} |
至少n次 | \d{2,} 匹配至少2个数字 |
{n,m} |
n次到m次 | \d{3,5} 匹配3到5个数字 |
为什么我说这个很重要?你想想看,匹配手机号需要11位数字,如果不用量词约束,那12位的、13位的都可能被误捕。用 \d{11} 就精确了。我记得有一次做金融数据脱敏,就靠这个精准量词把16位信用卡号从大段文本里捞出来的。
避坑指南:默认情况下,* 和 + 是"贪婪"的,它们会尽可能多地匹配。比如用 a.*b 去匹配 aXbYb,它会吞掉整个字符串。如果你想要"非贪婪"模式(尽可能少匹配),可以在量词后面加个 ?,写成 a.*?b。这在提取HTML标签内容时特别有用。
4. 分组与捕获:把匹配结果装进"篮子"
分组用小括号 (),它有两个作用:一是把多个符号当成一个整体来操作,二是把匹配到的内容"捕获"下来,方便后续使用。
// 分组示例
(ab)+ // 匹配 "ab" 的重复,如 "ababab"
(https?://) // 匹配 "http://" 或 "https://",并把协议部分捕获下来
// 反向引用:用 \1、\2 等引用前面捕获的内容
(\w+)\s+\1 // 匹配重复的单词,如 "hello hello"
反向引用在数据质量检查里很好用。我曾经用它来检测重复的字段值——比如两份数据合并后,同一个身份证号出现了两次,那肯定是脏数据。
| 分组类型 | 语法 | 是否捕获 | 用途 |
|---|---|---|---|
| 捕获分组 | (pattern) |
是 | 提取数据、反向引用 |
| 非捕获分组 | (?:pattern) |
否 | 只需要分组,不需要提取内容 |
| 零宽断言(前瞻) | (?=pattern) |
否 | 匹配某个位置后面跟着特定模式的情况 |
| 零宽断言(后顾) | (?<=pattern) |
否 | 匹配某个位置前面是特定模式的情况 |
重点记忆:在替换操作中,捕获组特别有用。比如把 John Doe 换成 Doe, John,可以用 (\w+)\s(\w+) 捕获,然后替换成 $2, $1。这在清洗姓名、地址等敏感字段时能省大量时间。
对了,非捕获分组 (?:...) 的实际应用场景:你需要把一组模式当成整体施加量词,但不需要保留中间匹配结果。比如匹配 (?:https?://)?\w+\.\w+ 来提取URL——前面的协议部分可有可无,捕获它没有意义,用非捕获分组性能更好。
5. 把这些零件组装起来:实战例子
光说不练假把式。咱们用敏感数据识别中最常见的几个模式来练练手。
// 手机号(中国大陆)
^1[3-9]\d{9}$
// 解析:以1开头,第二位3-9,后面9位数字,总共11位
// 身份证号(18位)
^[1-9]\d{5}(19|20)\d{2}(0[1-9]|1[0-2])(0[1-9]|[12]\d|3[01])\d{3}[\dXx]$
// 解析:包含了地区码、年份、月份、日期、顺序码和校验位
// 邮箱(简化版)
^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$
// 解析:用户名部分 + @ + 域名部分 + 顶级域名
// IP地址(IPv4,严格版)
^(?:(?:25[0-5]|2[0-4]\d|[01]?\d\d?)\.){3}(?:25[0-5]|2[0-4]\d|[01]?\d\d?)$
// 解析:每段数字0-255,用点分隔,共4段
我的调试习惯:正则不要一次性写太长。我习惯先用 . 测试基础匹配范围,然后加字符类缩小范围,再加量词控制长度,最后套上 ^$ 锚定边界。写一段、测一段,比最后回来改bug快得多。
你看,这一套组合拳下来,手机、身份证、邮箱、IP地址都能精确定位。在实际数据安全项目里,正则就是第一道防线——把可疑的数据先圈出来,再交给机器学习模型做更细致的分类。
6. 记住几个容易踩的坑
- 点号不匹配换行:默认情况下,
.不匹配\n。如果你要跨行匹配,得用[\s\S]代替,或者开启s模式。 - 量词默认贪婪:前面说过了,
.*能吞就吞。如果发现匹配结果比预期长,试试加?变成非贪婪。 - 字符类里的特殊符号:除了
^(在开头)、-(表示范围)、\(转义)、](结束符)以外,其他元字符在[]内部都是普通字符。 - 分组编号从1开始:正则里的捕获组编号从1开始,
$0或\0代表整个匹配内容。
切记:在敏感数据识别场景中,正则表达式本身也可能泄露信息。比如你把公司内部的数据匹配规则写死在代码里并上传到公共仓库,那等于把筛选条件公开了。我一般在项目里把敏感正则单独抽成配置文件,加上访问控制。
好了,正则基础的四个零件就讲完了。你可能会觉得内容不少,但说实话,实际项目中翻来覆去用的就是这些。把它们当成积木块,多拼几次就熟了。