正则表达式详解
1、简介
(1)定义与本质
- 文本模式工具
- 正则表达式(Regular Expression,简称 Regex)是一种用于描述字符串规则的文本模式。它通过普通字符(如字母、数字)和元字符(特殊符号)组合,定义字符串的匹配逻辑。
- 示例:
\d{3}-\d{4} 可匹配 "123-4567" 这类电话号码。
- 核心逻辑
- 其本质是微型的文本处理语言,通过简洁的语法实现对字符串的复杂操作(如匹配、替换、提取),类似编程中的"字符串公式"。
(2)功能与应用场景
| 功能 |
作用 |
典型场景 |
| 匹配验证 |
检测字符串是否符合规则(如格式校验) |
表单输入验证(邮箱、手机号) |
| 文本搜索 |
在大量文本中定位符合模式的片段 |
日志分析、代码检索 |
| 替换与清洗 |
将匹配内容替换为指定文本 |
数据清洗(如删除多余空格) |
| 子串提取 |
从字符串中分离特定部分(如提取日期) |
爬虫数据解析、文本结构化 |
(3)核心特性与优势
- 跨平台通用性:支持几乎所有主流编程语言(Python、Java、JavaScript等)和文本工具(VS Code、Word)。
- 高效性与灵活性
- 简洁表达复杂规则:如
[A-Za-z]+ 匹配任意英文单词,远胜手动遍历字符。
- 高性能处理:优化引擎(如DFA/NFA)可快速处理大规模文本。
- 学习曲线陡峭:元字符(如
.*?、\b)和贪婪/非贪婪模式等概念需系统学习。
(4)历史与发展
- 起源:1956年数学家Stephen Kleene提出正则集合理论,描述神经网络模型。
- 实用化:1970年代Unix之父Ken Thompson将其引入编辑器
ed和grep工具。
- 普及:1990年后成为编程语言标准组件(如Perl、Python)。
(5)实践工具
2、元字符
(1)基础
| 元字符 |
作用描述 |
示例 |
匹配结果 |
. |
匹配除换行符外任意单字符 |
a.c |
"abc"、"a1c" |
\ |
转义特殊字符 |
\. |
匹配点号"."而非通配符 |
(2)字符类
| 元字符 |
功能说明 |
等价形式 |
示例匹配 |
\d |
匹配数字 |
[0-9] |
"5"(在"A5"中) |
\w |
匹配单词字符 |
[a-zA-Z0-9_] |
"H"、"1"(在"Hello_1"中) |
\s |
匹配空白符 |
[ \t\n\r\f] |
空格、Tab符 |
[abc] |
匹配指定字符集 |
- |
"b"(在"abc"中) |
[^abc] |
匹配非指定字符 |
- |
"d"(在"abcd"中) |
[a-z] |
匹配字符范围 |
- |
"c"(在"a-c"范围内) |
(3)量词
| 元字符 |
匹配规则 |
示例 |
匹配结果 |
* |
0次或多次 |
ab*c |
"ac"、"abbc" |
+ |
1次或多次 |
a+b |
"ab"、"aaab" |
? |
0次或1次 |
colou?r |
"color"、"colour" |
{n} |
精确n次 |
o{2} |
"food"中的"oo" |
{n,} |
至少n次 |
a{3,} |
"aaa"、"aaaaa" |
{n,m} |
n到m次 |
d{2,4} |
"dd"、"dddd" |
(4)边界与位置
| 元字符 |
功能说明 |
示例 |
匹配位置 |
^ |
匹配行首 |
^Start |
仅匹配行首"Start" |
$ |
匹配行尾 |
end$ |
仅匹配行尾"end" |
\b |
单词边界 |
\bcat\b |
匹配独立单词"cat" |
\B |
非单词边界 |
\Bcat\B |
匹配"scattered"中的"cat" |
(5)分组与逻辑
| 元字符 |
作用描述 |
示例 |
匹配结果 |
() |
捕获分组 |
(ab)+ |
匹配"abab"整体 |
(?: ) |
非捕获分组 |
`(?:ab |
cd)` |
| ` |
` |
逻辑"或" |
`cat |
(6)零宽断言
| 元字符 |
功能说明 |
示例 |
匹配位置 |
(?=exp) |
正向先行断言(右侧需匹配 exp) |
\d+(?=px) |
"100px" → 提取数字"100" |
(?<=exp) |
正向后行断言(左侧需匹配 exp) |
(?<=¥)\d+ |
"¥200" → 提取"200" |
(?!exp) |
负向先行断言(右侧不能匹配 exp) |
\d{3}(?!\d) |
"123a" → 匹配结尾数字"123" |
(?<!exp) |
负向后行断言(左侧不能匹配 exp) |
(?<!USD)\d+ |
"EUR200" → 匹配非美元数字"200" |
3、修饰符
(1)核心通用
| 修饰符 |
作用 |
示例 |
匹配效果 |
i |
忽略大小写 |
/abc/i |
匹配 "abc"、"AbC"、"ABC" |
g |
全局匹配 |
/a/g |
在 "a1a2a3" 中匹配全部 "a"(默认仅匹配第一个) |
m |
多行模式 |
/^abc/m |
对多行文本每行单独匹配行首 "abc"(默认仅匹配整个文本开头) |
s |
单行模式(DotAll) |
/a.b/s |
允许 . 匹配换行符(如 "a\nb") |
m 与 s 常配合使用:m 处理行边界(^/$),s 处理跨行内容(.匹配换行)
- 修饰符可组合:如
/pattern/gi(全局+忽略大小写)
(2)特定语言
| 修饰符 |
作用 |
示例 |
e |
将匹配结果作为代码执行(PHP 5.5+ 弃) |
preg_replace('/a/', 'b', $str, 1, e) |
U |
非贪婪模式(逆转量词的贪婪行为) |
/a.*b/U 匹配最短结果(如 "a1b" in "a1b a2b") |
D |
严格结尾匹配($ 不匹配换行符前位置) |
/abc$/D 不匹配 "abc\n" |
| 修饰符 |
作用 |
支持版本 |
y |
粘性匹配(从 lastIndex 位置开始) |
ES6+ |
u |
Unicode 模式(支持 UTF-16 代理对) |
ES6+ |
| 修饰符 |
等效常量 |
作用 |
| - |
re.IGNORECASE |
同 i(忽略大小写) |
| - |
re.DOTALL |
同 s(. 匹配换行符) |
(3)应用场景
- 提取多行日志:
/^Error:.*/gm(匹配所有以 "Error:" 开头的行)
- 跨行HTML标签匹配:
/<div>.*<\/div>/s(s 使 . 包含换行符)
(4)注意事项
- 安全性:PHP 的
e 修饰符易引发代码注入,应避免使用
- 兼容性:后行断言(如
(?<=...))在旧版 JavaScript 中不支持 [历史对话]
- 性能:滥用
s 或 .* 可能引发回溯爆炸,需限定范围(如 [\s\S])
4、运算符优先级
(1)优先级总表
| 优先级 |
运算符类型 |
运算符示例 |
作用说明 |
| 1 |
转义符 |
\d, \., \\ |
最高优先级,用于取消元字符的特殊含义 |
| 2 |
分组与字符集 |
( ), (?: ), (?= ), [ ] |
圆括号定义子表达式,方括号定义字符类 |
| 3 |
量词(限定符) |
*, +, ?, {n}, {n,m} |
控制前面元素的重复次数(贪婪模式) |
| 4 |
位置锚点与序列 |
^, $, \b, \B, 普通字符 |
匹配位置(如行首/行尾)或字符序列(如abc按顺序连接) |
| 5 |
逻辑或 |
` |
` |
- 注:相同优先级从左到右运算(如
a|b|c 等效于 (a|b)|c)
(2)关键规则详解
- 分组 vs 量词优先级冲突
- 错误示例:
^ab|cd$
- 预期:匹配以
ab 开头或以 cd 结尾的字符串
- 实际:因
| 优先级最低,等效于 (^ab)|(cd$),可能匹配到非预期结果(如 "cd" 在行中)
- 修正:显式分组 →
^(ab|cd)$
- 字符集内部的特殊规则
- 方括号
[...] 内多数元字符失去特殊含义(如 [.*] 匹配字符 . 或 *)
- 例外:
^(取反)、-(范围)、\(转义)需特殊处理(如 [\^\-] 匹配 ^ 或 -)
- 量词的贪婪陷阱
- 默认贪婪匹配:
a.*b 在 "a1b a2b" 中匹配整个字符串
- 非贪婪模式:
a.*?b 仅匹配 "a1b"(量词后加 ?)
(3)实战避坑指南
| 场景 |
错误写法 |
正确写法 |
原因 |
| 匹配邮箱或手机号 |
`^abc |
def@com$` |
`^(abc |
| 提取带小数点的数字 |
\d+.\d+ |
\d+\.\d+ |
未转义 .(. 是通配符) |
| 匹配3位数字或小写字母 |
`[0-9] |
{3}[a-z]` |
`([0-9]{3}) |
(4)引擎差异注意
- JavaScript:不支持后行断言
(?<= ),部分版本不支持 s 修饰符(单行模式)
- PHP:特有
U 修饰符可反转贪婪行为(如 /a.*b/U 非贪婪)
- Python:用
re.DOTALL 代替 s 修饰符
(5)经典冲突案例
plain
复制代码
目标:匹配 "100px" 或 "200em"
错误: ^\d+px|em$ → 匹配 "100px" 或行尾的 "em"
正确: ^(\d+(px|em))$ → 完整匹配单位字符串
5、分组与引用
(1)分组类型
- 捕获分组
- 语法:
(pattern)
- 作用:匹配内容并分配编号(从左到右按左括号顺序编号)
- 示例:
plain
复制代码
(\d{4})-(\d{2})-(\d{2}) # 匹配日期"2023-05-20"
复制代码
* 分组1:`2023`(年份)
* 分组2:`05`(月份)
* 分组3:`20`(日期)
- 非捕获分组
- 语法:
(?:pattern)
- 作用:仅分组不存储内容,提升性能
- 示例:
plain
复制代码
(?:Mr|Ms)\.\w+ # 匹配"Mr.Smith"但不捕获"Mr"
复制代码
* 避免占用分组编号,适合纯逻辑组合
- 命名分组
- 语法:
- Python:
(?P<name>pattern)
- JavaScript:
(?<name>pattern)
- 作用:通过名称而非编号引用分组,增强可读性
- 示例:
plain
复制代码
(?P<year>\d{4})-(?P<month>\d{2}) # 匹配日期并命名分组
复制代码
* 引用时使用 `?P=year`(Python)或 `\k<year>`(JavaScript)
(2)引用方式
- 反向引用(匹配时引用)
- 语法:
- 按编号引用:
\1, \2(通用)
- 按名称引用:
(?P=name)(Python)、\k<name>(JavaScript)
- 作用:在表达式中复用前面分组匹配的内容
- 示例:
plain
复制代码
(\w+)\s+\1 # 匹配重复单词如"hello hello"
plain
复制代码
<([a-z]+)>.*?</\1> # 匹配成对HTML标签如"<div>...</div>"
- 替换引用(替换时引用)
- 语法:
- 通用:
$1, $2
- Python:
\g<name>
- 作用:在替换操作中复用分组内容
- 示例(Python日期格式转换):
python
复制代码
import re
new_text = re.sub(r'(\d{4})-(\d{2})-(\d{2})', r'\2/\3/\1', "2023-05-20")
# 结果:"05/20/2023"[[5]()]
(3)应用场景
| 场景 |
正则示例 |
作用 |
| 重复单词匹配 |
\b(\w+)\b\s+\1\b |
匹配"bye bye"而非"goodbye"[5] |
| 数据清洗 |
(\d{3})-(\d{4}) → $1$2 |
删除电话号码中的短横线 |
| 结构化日志提取 |
^Error: (.*?) in (file:\w+) |
提取错误信息和文件名 |
(4)注意事项
- 性能优化:避免嵌套过多捕获分组,非必要场景用
(?:) 替代 ()。
- 嵌套分组编号:嵌套分组按左括号顺序编号,例如
(a(b)):
- 语言差异:
- 命名分组语法在 Python/JavaScript 中不同。
- 旧版 JavaScript 不支持后行断言。
6、选择与分支
(1)基础语法
- 分支操作符
|:逻辑"或",匹配多个模式中的任意一个
plain
复制代码
cat|dog # 匹配 "cat" 或 "dog"
gr(a|e)y # 匹配 "gray" 或 "grey"(需分组限定范围)
plain
复制代码
错误示例: `^Error|Warning` → 匹配 "Error" 开头 或 任意位置的 "Warning"
正确写法: `^(Error|Warning)` → 仅匹配行首的 "Error" 或 "Warning"
- 分支 vs 字符集
[]:分支可处理不同长度的模式,字符集仅选单个字符
(2)进阶技巧
- 分支执行原理
- 左优先原则:引擎按从左到右顺序尝试分支,首个匹配成功即停止
- 优化建议:将长模式放左侧,减少回溯
plain
复制代码
`apple|app` 匹配 "application" → 优先匹配 "app",可能导致 "apple" 未被触发
- 非捕获分组优化:用
(?: ) 避免存储匹配内容,提升性能
plain
复制代码
`(?:http|ftp)://\w+` # 匹配协议类型但不存储
plain
复制代码
`(1[0-2]|0?[1-9]):([0-5]\d)` # 匹配小时(01-12)和分钟(00-59)
(3)实战场景
plain
复制代码
\w+@(gmail|outlook|qq)\.com
复制代码
- 时间格式匹配(24小时制):
plain
复制代码
([01]\d|2[0-3]):[0-5]\d # 匹配 "09:30"、"23:59",排除 "24:00"
(4)注意事项
| 陷阱 |
修正方案 |
原因 |
| 分支范围过广 |
用 () 明确边界:`(a |
b)c` |
| 贪婪匹配干扰 |
非贪婪量词:.*? |
防止 `a.* |
| 忽略大小写 |
添加修饰符 /i:`/(cat |
dog)/i` |
- 性能关键点
- 避免超多分支:如超过5个分支,考虑拆分为多个正则
- 预编译正则:在代码中预存正则对象(如Python的
re.compile)提升效率
7、断言
(1)本质与特性
- 零宽度匹配
- 断言仅匹配位置(如字符边界、特定条件的位置),不消耗实际字符。
- 例:
(?=px)\d+ 匹配 "100px" 中的 "100"((?=px) 验证右侧是 "px",但不包含它)。
- 核心作用
- 精准定位:在复杂文本中提取符合特定上下文规则的内容(如数字后跟单位)。
- 条件过滤:排除不符合前后文的内容(如排除 "that" 后的 "this")。
(2)四大类型
- 正向先行断言(Lookahead)
- 语法:
(?=pattern)
- 作用:匹配位置右侧需满足
pattern
plain
复制代码
\d+(?=px) # 匹配 "100px" 中的 "100"(右侧需是 "px")
- 负向先行断言(Negative Lookahead)
- 语法:
(?!pattern)
- 作用:匹配位置右侧不能满足
pattern
plain
复制代码
\d{3}(?!\d) # 匹配 "123a" 中的 "123"(右侧不能是数字)
- 正向后行断言(Lookbehind)
- 语法:
(?<=pattern)
- 作用:匹配位置左侧需满足
pattern
plain
复制代码
(?<=\$)\d+ # 匹配 "$200" 中的 "200"(左侧需是 "$")
- 负向后行断言(Negative Lookbehind)
- 语法:
(?<!pattern)
- 作用:匹配位置左侧不能满足
pattern
plain
复制代码
(?<!\d)\.\d+ # 匹配 "a.5" 中的 ".5"(左侧不能是数字,避免匹配 IP 地址)
(3)典型应用场景
plain
复制代码
(?<=\$)\d+\.\d{2} # 匹配 "$12.30" → "12.30"
- 内容过滤:匹配 "this" 但排除含 "that" 的句子
plain
复制代码
^((?!that).)*this((?!that).)*$ # 匹配 "this is valid" 而非 "that is this"
- 密码强度验证:要求包含大写字母、数字,且长度 8-10 位
plain
复制代码
^(?=.*[A-Z])(?=.*\d).{8,10}$ # 验证 "Pass1234"
(4)注意事项
- 兼容性问题
- 后行断言(
?<=/?<!)在 JavaScript 旧版本中不支持。
- 解决方案:使用捕获分组替代(如
(\$)(\d+) 提取数字)。
- 性能优化
- 避免在长文本中嵌套复杂断言(如
(?=.*a)(?=.*b)),可能引发回溯爆炸。
- 尽量限定范围:用
\w 替代 .,用 {n} 替代 */+。
- 语法限制:后行断言中的
pattern 需为固定长度(如 (?<=\d{3}) 有效,(?<=\w+) 无效)。
8、常用案例
(1)数字与金额验证
- 整数(正/负/零)
- 正整数(非零开头)
- 金额(保留1-2位小数)
^[1-9]\d*(\.\d{1,2})?$
- 支持
100.50,排除 0.00
- 百分比(0%-100%,可含小数)
^(100(\.0+)?|\d{1,2}(\.\d+)?)%$
- 匹配
75%、99.5%
(2)文本与字符规则
- 中文字符
^[\u4e00-\u9fa5]+$
- 纯中文校验,如
"中文"
- 英文数字组合(不含符号)
^[A-Za-z0-9]+$
- 如
"abc123"
- 密码强度(8-20位,含大小写+数字+特殊字符)
^(?=.*\d)(?=.*[a-z])(?=.*[A-Z])(?=.*[\W_]).{8,20}$
- 预查确保四类字符必现
- 用户名(字母开头,5-16位)
^[a-zA-Z][a-zA-Z0-9_]{4,15}$
- 允许数字和下划线
- 删除首尾空格
(3)网络与标识符
- 邮箱地址
^\w+([-+.]\w+)*@\w+([-.]\w+)*\.\w+([-.]\w+)*$
- 支持
user.name+tag@domain.co.uk
- URL链接
^(https?://)?([\w-]+\.)+[\w-]+(/[\w-./?%&=]*)?$
- 兼容
http/https 及路径参数
- IPv4地址
^((25[0-5]|2[0-4]\d|1\d{2}|[1-9]?\d)\.){3}(25[0-5]|2[0-4]\d|1\d{2}|[1-9]?\d)$
- 每段≤255,严格校验
- 中国大陆手机号
^1(3\d|4[5-9]|5[0-35-9]|6|7[0-8]|8\d|9[0-35-9])\d{8}$
- 覆盖主流运营商号段
(4)日期与证件
- 日期(YYYY-MM-DD,闰年兼容)
^(?:(?!0000)[0-9]{4}-(?:0[1-9]|1[0-2])-(?:0[1-9]|[12][0-9]|3[01])$
- 校验
2023-02-28 有效性
- 身份证号(15/18位,末位可X)
^(\d{15}|\d{17}[\dXx])$
- 简单格式验证
- 中国大陆邮编
(5)进阶处理技巧
- 提取HTML图片链接
<img\s+[^>]*src="([^"]*)"
- 捕获
src 属性值
- 匹配中文标点
[\u3002\uff1b\uff0c\uff1a\u201c\u201d\uff08\uff09\u3001\uff1f\u300a\u300b]
- 如
。;:""() 等
- 连续重复词检测
\b(\w+)\b\s+\1\b
- 查找如
"the the" 的错误
- 十六进制颜色码
^#([A-Fa-f0-9]{6}|[A-Fa-f0-9]{3})$
- 支持
#FFF 和 #FFFFFF
(6)实用工具类
- 空白行匹配
- 腾讯QQ号
^[1-9][0-9]{4,10}$
- 5-11位,首位非零
- 非捕获分组示例
- 匹配文件扩展名
- XML/HTML标签
<([a-z]+)([^<]+)*(?:>(.*?)</\1>|\s+/>)
- 匹配开闭标签内容
(7)其他高频需求
- Base64字符串
^[A-Za-z0-9+/]+={0,2}$
- 校验编码格式合法性
- MAC地址
^([0-9A-Fa-f]{2}:){5}[0-9A-Fa-f]{2}$
- 如
00:1A:2B:3C:4D:5E
- 车牌号(中国大陆)
^[京津沪渝冀豫云辽黑湘皖鲁新苏浙赣鄂桂甘晋蒙陕吉闽贵粤青藏川宁琼][A-HJ-NP-Z][A-HJ-NP-Z0-9]{4,5}[A-HJ-NP-Z0-9挂学警港澳]$
- 兼容新能源车牌
- 排除特定字符(如不含
@#)
- 匹配连续相同字符(如
aaa)