正则表达式从入门到精通(字符串模式匹配)

前言:正则表达式的价值与应用场景

正则表达式(Regular Expression,简称 Regex)是一种用于描述字符串模式的语法规则,本质是 "匹配字符串的数学公式",广泛应用于:

  • 文本处理:数据提取、格式校验、内容替换、字符串分割;
  • 开发场景:日志分析、代码语法检查、用户输入验证(手机号、邮箱、密码);
  • 运维 / 爬虫:日志过滤、网页数据抓取、配置文件解析;
  • 办公自动化:Excel 数据清洗、Word 文档批量替换。

只要涉及文本的匹配、提取、验证、替换,正则表达式就能用极简的语法,替代数十行甚至上百行的原生代码逻辑 。

一:正则表达式基础

1.1 正则表达式的本质与核心思想

正则表达式的核心是 "模式匹配"------ 用预先定义的 "规则字符串" 去匹配目标字符串中的内容。例如:

  • 规则 \d{11} 可匹配 11 位数字(手机号);
  • 规则 [a-zA-Z0-9_-]+@[a-zA-Z0-9_-]+\.[a-z]{2,4} 可匹配邮箱格式。

核心思想:将复杂的字符串规则抽象为简洁的正则语法,通过 "元字符""量词""边界" 等组合,实现精准匹配。

1.2 正则表达式的两种定义方式(多语言对比)

所有语言的正则都支持两种定义方式,核心区别在于是否支持动态拼接:

|--------|-------------------------------------------------------|---------------|------------------------|
| 定义方式 | 语法格式(以匹配数字为例) | 适用场景 | 主流语言支持情况 |
| 字面量写法 | Python: r'\d' / JS: /\d/ | 规则固定不变时(推荐) | Python、JS、Perl、PHP 等支持 |
| 构造函数写法 | Python: re.compile('\\d') / JS: new RegExp('\\d') | 规则需动态拼接(如含变量) | 所有语言均支持 |

注意:Python 中字符串的 \ 是转义符,因此正则中的 \d 需写成 \\d,或用原始字符串 r'\d'(推荐,避免双转义)。

1.3 基础元字符:匹配单个字符

元字符是正则的 "基本 building block",用于匹配单个字符或字符类别,以下是所有基础元字符的详细说明:

|------------------|--------------------------------------|---------------------------|--------------------------|
| 元字符 | 作用描述 | 示例 | 匹配结果 |
| 普通字符(a-z、0-9、等) | 匹配字符本身 | abc 匹配 abc123 | abc |
| . | 匹配任意单个字符(默认不匹配换行符 \n) | a.c 匹配 abc a1c a#c | abc、a1c、a#c |
| \d | 匹配任意数字(等价于 [0-9]) | \d 匹配 a1b2c3 | 1、2、3 |
| \D | 匹配非数字(等价于 [^0-9]) | \D 匹配 1a2b3c | a、b、c |
| \w | 匹配单词字符(字母、数字、下划线,等价于 [a-zA-Z0-9
]) | \w 匹配 a_1#b | a、、1、b |
| \W | 匹配非单词字符(等价于 [^a-zA-Z0-9
]) | \W 匹配 a_1#b | #、 |
| \s | 匹配空白符(空格、制表符 \t、换行符 \n、回车符 \r) | a\sb 匹配 a b a\tb | a b、a\tb |
| \S | 匹配非空白符 | a\Sb 匹配 a1b a#b | a1b、a#b |
| \b | 单词边界(匹配单词开头 / 结尾,不占字符位置) | \bcat\b 匹配 cat a cat b | cat(不匹配 category 中的 cat) |
| \B | 非单词边界 | \Bcat\B 匹配 category | cat(仅匹配单词中间的 cat) |
| ^ | 字符串开头(多行模式下匹配行首) | ^abc 匹配 abc123 abc\n456 | abc(第一行开头) |
| | 字符串结尾(多行模式下匹配行尾) | 123 匹配 abc123 456\n123 | 123(最后一行结尾) |
| \ | 转义符(将特殊字符转为普通字符) | \. 匹配 a.b | .(避免 . 被当作元字符) |

示例 1:基础元字符实战(Python)

python 复制代码
import re

str1 = "Hello_World 123! 456\n789"
# 匹配所有单词字符
print(re.findall(r'\w', str1))  # ['H','e','l','l','o','_','W','o','r','l','d','1','2','3','4','5','6','7','8','9']
# 匹配所有空白符
print(re.findall(r'\s', str1))  # [' ', ' ', '\n']
# 匹配字符串开头的Hello
print(re.findall(r'^Hello', str1))  # ['Hello']
# 匹配字符串结尾的789
print(re.findall(r'789$', str1))  # ['789']
# 匹配点号(需转义)
str2 = "a.b.c"
print(re.findall(r'\.', str2))  # ['.', '.']

1.4 字符集:匹配多个候选字符中的一个

字符集用 [] 表示,用于匹配 "集合内任意一个字符",支持范围表示和排除逻辑:

1.4.1 基础字符集

语法:[字符1字符2...] 或 [字符范围]

示例:

  • abc\]:匹配 a、b、c 中的任意一个;

  • A-Z\]:匹配大写字母;

  • a-zA-Z0-9\]:匹配字母和数字(等价于 \\w 去掉下划线)。

语法:[^字符1字符2...](^ 在 [] 内表示 "排除")

示例:

  • \^abc\]:匹配除 a、b、c 之外的任意字符;

1.4.3 字符集的特殊规则

  1. \] 内的元字符会失效,无需转义(除 \^、-、\] 外):

  • -\] 或 \[a-z-\] 匹配减号(将 - 放在开头 / 结尾或转义 \\-)。

示例 2:字符集实战(Python)

python 复制代码
import re

str = "Google Runoob Taobao 123! 456"
# 匹配所有元音字母(a/e/i/o/u,忽略大小写)
patt1 = r'[aeiouAEIOU]'
print(re.findall(patt1, str))  # ['o','o','e','u','o','o','a','o','a','o']
# 匹配所有非字母字符
patt2 = r'[^a-zA-Z]'
print(re.findall(patt2, str))  # [' ',' ',' ',' ','1','2','3','!',' ','4','5','6']
# 匹配数字或下划线
patt3 = r'[0-9_]'
print(re.findall(patt3, str))  # ['1','2','3','4','5','6']

1.5 量词:匹配字符出现的次数

量词用于指定 "前面的元素(字符、字符集、分组)出现的次数",是正则简化重复规则的核心,以下是所有量词的详细说明:

|--------|--------------------------|----------|-----------------------|-------------------------|
| 量词 | 作用描述 | 贪婪 / 非贪婪 | 示例 | 匹配结果 |
| * | 匹配前面的元素 0 次或多次(无限次) | 贪婪 | ab* 匹配 a ab abb abbb | a、ab、abb、abbb |
| + | 匹配前面的元素 1 次或多次(至少 1 次) | 贪婪 | ab+ 匹配 ab abb abbb | ab、abb、abbb |
| ? | 匹配前面的元素 0 次或 1 次(可选) | 贪婪 | ab? 匹配 a ab | a、ab |
| {n} | 匹配前面的元素恰好 n 次 | 固定 | a{3} 匹配 aaa aaaa | aaa(aaaa 中匹配前 3 个 a) |
| {n,} | 匹配前面的元素至少 n 次(n 到无限次) | 贪婪 | a{2,} 匹配 aa aaa aaaa | aa、aaa、aaaa |
| {n,m} | 匹配前面的元素 n 到 m 次(含 n 和 m) | 贪婪 | a{2,3} 匹配 aa aaa aaaa | aa、aaa(aaaa 中匹配前 3 个) |
| *? | 0 次或多次(非贪婪,尽可能少匹配) | 非贪婪 | ab*? 匹配 a ab abb | a、ab、ab(abb 中仅匹配 1 个 b) |
| +? | 1 次或多次(非贪婪) | 非贪婪 | ab+? 匹配 ab abb | ab、ab(abb 中仅匹配 1 个 b) |
| ?? | 0 次或 1 次(非贪婪) | 非贪婪 | ab?? 匹配 a ab | a、a(ab 中不匹配 b) |
| {n,m}? | n 到 m 次(非贪婪) | 非贪婪 | a{2,3}? 匹配 aaa aaaa | aa、aa(aaaa 中匹配前 2 个) |

关键概念:贪婪匹配 vs 非贪婪匹配

  • 贪婪匹配(默认):量词会尽可能多地匹配字符,直到无法匹配为止;
  • 非贪婪匹配:在量词后加 ?,量词会尽可能少地匹配字符,满足条件即可。

示例 3:贪婪与非贪婪对比(Python)

python 复制代码
import re

str1 = "aaaaa"
# 贪婪匹配:{2,3} 尽可能匹配3个
print(re.findall(r'a{2,3}', str1))  # ['aaa', 'aa'](3个+2个)
# 非贪婪匹配:{2,3}? 尽可能匹配2个
print(re.findall(r'a{2,3}?', str1))  # ['aa', 'aa', 'a'](2个+2个+1个不满足,舍弃)

str2 = "<div>内容1</div></div>"
# 贪婪匹配:.* 会匹配到最后一个 >
print(re.findall(r'<div>.*</div>', str2))  # ['1</div>内容2(整体匹配)
# 非贪婪匹配:.*? 匹配到第一个 </div>
print(re.findall(r'?  # ['内容1</div>', '<div>内容2>'](分开匹配)

1.6 分组与选择:复杂模式的组合

1.6.1 分组(()):将多个元素视为一个整体

作用:1. 把多个字符 / 元字符组合成一个 "单元",配合量词使用;2. 捕获匹配结果(后续可引用)。

示例:

  • (ab)+:匹配 ab、abab、ababab 等(ab 作为整体重复);
  • (a|b)c:匹配 ac 或 bc(| 表示 "或",配合分组使用)。

1.6.2 选择(|):匹配多个模式中的一个

  • 语法:模式1|模式2|模式3(匹配任意一个模式);
  • 注意:| 的优先级最低,需用分组明确范围,否则会匹配 "模式 1" 或 "模式 2 + 后续内容"。

反例:ab|cd 匹配 ab 或 cd(正确);a|bc 匹配 a 或 bc(而非 ab 或 bc)。

1.6.3 非捕获分组((?:)):仅分组,不捕获结果

  • 作用:当只需将元素组合成单元,无需捕获匹配内容时使用,可提升性能;
  • 区别于普通分组:普通分组 () 会将匹配结果存入内存,非捕获分组 (?:) 不存储。

示例 4:分组与选择实战(Python)

python 复制代码
import re

str = "abab 123 456 abcd"

# 1. 普通分组:匹配 (ab) 重复1次以上
patt1 = re.compile("(ab)+")
m1 = patt1.finditer(str)
for match in m1:
    print("普通分组匹配:" + match.group())  # 输出:abab

# 2. 选择匹配:匹配 123 或 456
patt2 = re.compile("123|456")
m2 = patt2.finditer(str)
for match in m2:
    print("选择匹配:" + match.group())  # 输出:123、456

# 3. 非捕获分组:匹配 (ab) 或 (cd),但不捕获分组内容
patt3 = re.compile("(?:ab)|(?:cd)")
m3 = patt3.finditer(str)
for match in m3:
    print("非捕获分组匹配:" + match.group())  # 输出:ab、ab、cd

1.7 修饰符(标志):调整匹配规则

修饰符用于修改正则的匹配行为,不同语言的表示方式不同,但功能一致,以下是常用修饰符的对比:

|-----|-------------|----------------------------------|--------------------|---------------------|--------------------------|
| 修饰符 | 全称 | 功能描述 | Python 写法 | JavaScript 写法 | Java 写法 |
| g | Global | 全局匹配,匹配所有符合规则的结果(而非仅第一个) | 无(通过 findall() 实现) | /pattern/g | 无(通过循环 find() 实现) |
| i | Ignore Case | 忽略大小写匹配 | re.I/re.IGNORECASE | /pattern/i | Pattern.CASE_INSENSITIVE |
| m | Multiline | 多行模式:^ 匹配行首,$ 匹配行尾(默认匹配整个字符串首尾) | re.M/re.MULTILINE | /pattern/m | Pattern.MULTILINE |
| s | Dotall | 点号模式:让 . 匹配所有字符(包括换行符 \n,默认不匹配) | re.S/re.DOTALL | /pattern/s(ES2018+) | Pattern.DOTALL |
| x | Verbose | 注释模式:允许正则中写注释(# 后面的内容为注释),提升可读性 | re.X/re.VERBOSE | 不支持 | Pattern.COMMENTS |

示例 5:修饰符实战(Python)

python 复制代码
# Python:re.I(忽略大小写)+ re.M(多行模式)
import re

str1 = "Hello\nhello\nHELLO"
# 忽略大小写,匹配所有 hello(无论大小写)
patt1 = re.compile(r'hello', re.I)
print(patt1.findall(str1))  # ['Hello', 'hello', 'HELLO']

# 多行模式:匹配每一行开头的 H/h
patt2 = re.compile(r'^h', re.I | re.M)
print(patt2.findall(str1))  # ['H', 'h', 'H']

# 点号匹配换行:匹配从 Hello 到 HELLO 的所有内容(re.S 修饰符)
patt3 = re.compile(r'Hello.*HELLO', re.S)
match = patt3.search(str1)
print(match.group())  # 输出整个字符串(包括换行)

二:正则表达式进阶

2.1 反向引用:复用之前的分组匹配结果

反向引用用于 "引用正则中已捕获的分组内容",语法为 \n(n 是分组编号,从 1 开始),核心应用场景:匹配重复出现的内容。

2.1.1 基础反向引用(数字引用)

语法:(模式)\1(\1 引用第一个分组的匹配结果);

示例:

  • (ab)\1:匹配 abab(第一个分组匹配 ab,\1 复用 ab);
  • (\d)\1:匹配 22、33 等重复数字;
  • ([a-zA-Z])(\d)\2\1:匹配 a22a、b55b 等(字母 + 数字 + 重复数字 + 重复字母)。

2.1.2 命名分组与反向引用

数字引用在分组较多时容易混淆,命名分组允许给分组起名字,语法为 (?P>模式)(Python),反向引用为 (?P=name)(Python)。

示例 6:反向引用实战(Python)

python 复制代码
# Python:数字反向引用 + 命名分组反向引用
import re

# 1. 匹配重复的两位数字(如 22、33、55)
str1 = "11 222 33 4444 55"
patt1 = r'(\d)\1'
print(re.findall(patt1, str1))  # ['1','2','3','4','5'](返回分组内容,需调整为匹配整体)
patt1_fix = r'(\d)\1+'  # 匹配1个以上重复数字
print(re.findall(patt1_fix, str1))  # ['1','2','3','4','5'](仍返回分组,需用 group())

# 用 match 对象获取整体匹配
for match in re.finditer(patt1_fix, str1):
    print(match.group())  # 输出:11、22、33、4444、55

# 2. 命名分组:匹配 abab 格式(命名分组为 ab_group)
patt2 = r'(?P)(?P=ab_group)'
str2 = "ab abab ababab"
print(re.findall(patt2, str2))  # ['ab', 'ab'](分组内容)
for match in re.finditer(patt2, str2):
    print(match.group())  # 输出:abab、abab

2.2 断言(零宽断言):匹配位置而非字符

断言(Assertion)用于 "判断某个位置前后是否满足特定条件",本身不匹配任何字符(零宽度),仅用于定位。断言分为四大类:正向先行断言、负向先行断言、正向后行断言、负向后行断言。

2.2.1 先行断言(Lookahead):判断位置后面的内容

  • 正向先行断言:(?=模式) ------ 位置后面必须匹配模式;
  • 负向先行断言:(?!模式) ------ 位置后面必须匹配模式。

示例

  • \d+(?=元):匹配 "元" 前面的数字(如 100元 中的 100);
  • \d+(?!元):匹配后面不是 "元" 的数字(如 100美元 中的 100)。

2.2.2 后行断言(Lookbehind):判断位置前面的内容

  • 正向后行断言:(?<=模式) ------ 位置前面必须匹配模式;
  • 负向后行断言:(?) ------ 位置前面必须匹配模式。

示例

  • (?)\d+:匹配 "¥" 后面的数字(如 ¥100 中的 100);
  • (?)\d+:匹配前面不是 "¥" 的数字(如 $100 中的 100)。

2.2.3 断言的核心特点

  1. 零宽度:断言匹配的是 "位置",而非字符,因此匹配结果中不会包含断言的内容;
  1. 不消耗字符:匹配后指针不移动,后续匹配从当前位置继续;
  1. 支持嵌套:断言中可以嵌套其他断言(如 (?{3})¥)\d+)。

示例 7:断言实战(Python)

python 复制代码
# Python:断言提取价格(支持后行断言,Python 3.7+)
import re

str1 = "苹果:¥5.99 香蕉:10元 橙子:$12.5 葡萄:8.8元"

# 1. 提取 ¥ 后面的价格(正向后行断言)
patt1 = r'(?.?\d*'
print(re.findall(patt1, str1))  # ['5.99']

# 2. 提取 元 前面的价格(正向先行断言)
patt2 = r'\d+\.?\d*(?=元)'
print(re.findall(patt2, str1))  # ['10', '8.8']

# 3. 提取非 ¥ 开头的价格(负向后行断言)
patt3 = r'(?d+\.?\d*(?=元)'
print(re.findall(patt3, str1))  # ['10', '8.8']

# 4. 提取不是 元 结尾的价格(负向先行断言)
patt4 = r'\d+\.?\d*(?!元)'
print(re.findall(patt4, str1))  # ['5.99', '12.5'](注意:10 和 8.8 后面是元,被排除)

2.3 特殊构造:条件匹配与 Unicode 支持

2.3.1 条件匹配((?(条件)模式1|模式2))

条件匹配根据 "分组是否匹配" 或 "断言是否成立" 选择匹配模式,语法:

  • (?(n)模式1|模式2):如果第 n 个分组匹配成功,匹配模式 1,否则匹配模式 2;
  • (?(name)模式1|模式2):如果命名分组 name 匹配成功,匹配模式 1,否则匹配模式 2;
  • (?(断言)模式1|模式2):如果断言成立,匹配模式 1,否则匹配模式 2。

示例 8:条件匹配实战(Python)

python 复制代码
import re

# 匹配:如果前面是 http,则匹配 ://,否则匹配 (空)
str1 = "http://runoob.com https://baidu.com ftp://qq.com"
patt1 = r'(http)?(?(1)://|)'  # 1是第一个分组(http),如果匹配则匹配 ://,否则匹配空
print(re.findall(patt1, str1))  # [('http', '://'), ('https', ''), ('', '')](注意:https 不匹配 http,因此第二个分组为空)

# 修正:匹配 http 或 https 后面的 ://
patt1_fix = r'(https?)(?(1)://|)'
print(re.findall(patt1_fix, str1))  # [('http', '://'), ('https', '://'), ('', '')]

2.3.2 Unicode 匹配(\p{})

Unicode 匹配用于匹配特定语言、脚本或符号,语法为 \p{属性},需配合 re.UNICODE 修饰符(Python)。

常用 Unicode 属性:

  • \p{L}:匹配任意字母(包括中文、英文、日文等);
  • \p{N}:匹配任意数字(包括中文数字、阿拉伯数字等);
  • \p{Z}:匹配任意空白符;
  • \p{Han}:匹配任意中文字符;
  • \p{P}:匹配任意标点符号。

示例 9:Unicode 匹配实战(Python)

python 复制代码
# Python:匹配中文字符和中文数字
import re

str1 = "你好,世界!123 四五六年级 ⅣⅤⅥ"
# 匹配中文字符(\p{Han})
patt1 = re.compile(r'\p{Han}+', re.UNICODE)
print(patt1.findall(str1))  # ['你好', '世界', '四五六年级']

# 匹配所有数字(阿拉伯数字+中文数字+罗马数字)
patt2 = re.compile(r'\p{N}+', re.UNICODE)
print(patt2.findall(str1))  # ['123', '四五六年级', 'ⅣⅤⅥ'](注意:中文数字"四五六年级"被识别为数字)

2.4 正则优化:性能与可读性提升技巧

2.4.1 性能优化原则

  1. 避免过度回溯:回溯是 NFA 引擎(主流引擎)的核心机制,但过度回溯会导致性能暴跌,需注意:
  • 用非贪婪量词替代贪婪量词(如 .*? 替代 .*);
  • 避免嵌套量词(如 (a+)* 会导致大量回溯);
  • 用断言替代复杂分组(如 (?=\d{11}) 提前判断长度,避免无效匹配)。
  1. 编译正则:多次使用的正则模式,先编译(re.compile()),提升执行效率。
  2. 精准匹配:避免使用过于宽泛的模式(如 .*),尽量用具体的字符集(如 [a-zA-Z0-9])。

2.4.2 可读性优化技巧

  1. 使用原始字符串:避免双转义,提升可读性(如 r'\d' 比 '\\d' 清晰);
  1. 非捕获分组:无需捕获的分组用 (?:),明确意图;
  1. 注释模式(re.X):复杂正则用注释说明每个部分的作用。

示例 10:优化后的复杂正则(Python,注释模式)

python 复制代码
import re

# 匹配邮箱:支持字母、数字、下划线、中划线,域名支持2-4位后缀
email_pattern = re.compile(r'''
    ^[a-zA-Z0-9_-]+       # 用户名:字母、数字、下划线、中划线
    @                     # @符号
    [a-zA-Z0-9_-]+        # 域名主体:字母、数字、下划线、中划线
    (\.[a-zA-Z0-9_-]+)*   # 二级域名:.com.cn 等
    \.[a-z]{2,4}          # 顶级域名:2-4位字母
    $
''', re.VERBOSE | re.IGNORECASE)  # 注释模式 + 忽略大小写

print(email_pattern.match("test-123@runoob.com"))  # 匹配成功(返回Match对象)
print(email_pattern.match("test@.com"))  # 匹配失败(返回None)

三:正则表达式实战(多场景应用)

3.1 数据提取:从文本中提取目标信息

数据提取是正则最常用的场景,包括提取数字、邮箱、手机号、URL、日期等。

3.1.1 提取手机号(中国大陆)

  • 规则:11 位数字,以 13/14/15/16/17/18/19 开头;
  • 正则:1[3-9]\d{9}。

示例 11:提取文本中的所有手机号(Python)

python 复制代码
import re

text = "我的手机号是13812345678,备用号19987654321,还有一个12345678901(无效)"
phone_pattern = r'1[3-9]\d{9}'
phones = re.findall(phone_pattern, text)
print(phones)  # ['13812345678', '19987654321'](12345678901 以12开头,被排除)

3.1.2 提取邮箱地址

  • 规则:用户名(字母、数字、下划线、中划线、点号)@ 域名(字母、数字、下划线、中划线、点号). 顶级域名(2-4 位字母);
  • 正则:[a-zA-Z0-9_-]+(?:\.[a-zA-Z0-9_-]+)*@[a-zA-Z0-9_-]+(?:\.[a-zA-Z0-9_-]+)*\.[a-z]{2,4}。

示例 12:提取文本中的所有邮箱(Python)

python 复制代码
import re

text = "我的邮箱是test123@runoob.com,工作邮箱是user-name.456@company.cn,还有invalid-email@.com(无效)"
email_pattern = r'[a-zA-Z0-9_-]+(?:\.[a-zA-Z0-9_-]+)*@[a-zA-Z0-9_-]+(?:\.[a-zA-Z0-9_-]+)*\.[a-z]{2,4}'
emails = re.findall(email_pattern, text, re.IGNORECASE)
print(emails)  # ['test123@runoob.com', 'user-name.456@company.cn']

3.1.3 提取 URL(网页链接)

  • 规则:以 http/https/ftp 开头,包含域名和路径;
  • 正则:(https?:\/\/|ftp:\/\/)?([a-zA-Z0-9_-]+\.)+[a-zA-Z0-9_-]+(:\d+)?(\/[^\s]*)?。

示例 13:提取文本中的所有 URL(Python)

python 复制代码
import re

text = "访问 https://www.runoob.com 学习正则,或 http://baidu.com:8080/search?kw=regex,也可以看 ftp://ftp.example.com/file.txt"
url_pattern = r'(https?:\/\/|ftp:\/\/)?([a-zA-Z0-9_-]+\.)+[a-zA-Z0-9_-]+(:\d+)?(\/[^\s]*)?'
urls = re.findall(url_pattern, text)
# 提取完整URL(因为分组原因,需拼接结果)
complete_urls = [''.join(url_parts) for url_parts in urls if ''.join(url_parts)]
print(complete_urls)  # ['https://www.runoob.com', 'http://baidu.com:8080/search?kw=regex', 'ftp://ftp.example.com/file.txt']

3.2 格式校验:验证用户输入合法性

格式校验用于判断用户输入是否符合要求(如密码强度、身份证号、日期格式等)。

3.2.1 验证密码强度(强密码)

  • 规则:8-20 位,包含大小写字母、数字、特殊字符(!@#$%^&*)中的至少三种;
  • 正则:^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[!@#%\^\&\*\]).{8,20}(四种都包含)或 ^(?![a-zA-Z]+)(?!\[A-Z0-9\]+)(?![A-Z!@#%\^\&\*\]+)(?![a-z0-9]+)(?!\[a-z!@#%^&*]+)(?!\[0-9!@#%^&*]+).{8,20}(至少三种)。

示例 14:验证密码强度(Python)

python 复制代码
import re

def check_password(password):
    # 强密码规则:8-20位,至少包含大小写字母、数字、特殊字符中的三种
    pattern = r'^(?![a-zA-Z]+$)(?![A-Z0-9]+$)(?![A-Z!@#$%^&*]+$)(?![a-z0-9]+$)(?![a-z!@#$%^&*]+$)(?![0-9!@#$%^&*]+$).{8,20}$'
    return re.match(pattern, password) is not None

print(check_password("Pass123!"))  # True(包含大小写、数字、特殊字符)
print(check_password("pass1234"))  # False(仅小写字母+数字,两种)
print(check_password("PASSWORD!"))  # False(仅大写字母+特殊字符,两种)

3.2.2 验证身份证号(中国大陆 18 位)

  • 规则:18 位,前 17 位为数字,第 18 位为数字或 X(大小写均可);
  • 正则:^[1-9]\d{5}(19|20)\d{2}(0[1-9]|1[0-2])(0[1-9]|[12]\d|3[01])\d{3}[\dXx]$。

示例 15:验证身份证号(Python)

python 复制代码
import re

def check_id_card(id):
    pattern = r'^[1-9]\d{5}(19|20)\d{2}(0[1-9]|1[0-2])(0[1-9]|[12]\d|3[01])\d{3}[\dXx]$'
    return re.match(pattern, id) is not None

print(check_id_card("110101199001011234"))  # True
print(check_id_card("11010119900101123X"))  # True
print(check_id_card("1101011990010112345"))  # False(19位)

3.3 文本替换:批量修改字符串内容

文本替换用于批量修改符合规则的内容(如敏感词过滤、格式标准化、冗余内容删除)。

3.3.1 敏感词过滤(替换为 *)

示例 16:过滤敏感词(Python)

python 复制代码
import re

def filter_sensitive_words(text, sensitive_words):
    # 敏感词拼接为正则(用|分隔,re.escape处理特殊字符)
    pattern = r'(' + '|'.join(re.escape(word) for word in sensitive_words) + ')'
    # 替换为同等长度的*
    return re.sub(pattern, lambda m: '*' * len(m.group()), text)

text = "这个混蛋竟然在公共场合说脏话,太无耻了!"
sensitive_words = ["混蛋", "脏话", "无耻"]
filtered_text = filter_sensitive_words(text, sensitive_words)
print(filtered_text)  # 输出:这个**竟然在公共场合说**,太**了!

3.3.2 格式化日期(将 2024-05-20 转为 2024 年 05 月 20 日)

示例 17:日期格式转换(Python)

python 复制代码
import re

text = "今天是2024-05-20,明天是2024-05-21"
pattern = r'(\d{4})-(\d{2})-(\d{2})'
# 替换为 \1年\2月\3日(\1 引用第一个分组,即年份)
formatted_text = re.sub(pattern, r'\1年\2月\3日', text)
print(formatted_text)  # 输出:今天是2024年05月20日,明天是2024年05月21日

3.4 字符串分割:按复杂规则分割字符串

字符串分割用于按正则规则分割字符串(比普通 split() 支持更复杂的分隔符)。

3.4.1 按任意空白符分割(空格、制表符、换行符)

示例 18:按空白符分割(Python)

python 复制代码
import re

text = "Hello   World\tPython\nRegex"  # 包含多个空格、制表符、换行符
result = re.split(r'\s+', text)
print(result)  # ['Hello', 'World', 'Python', 'Regex']

3.4.2 按数字或逗号分割

示例 19:按数字或逗号分割(Python)

python 复制代码
import re

text = "a1b22c333d,444e555f"
result = re.split(r'\d+|,', text)
print(result)  # ['a', 'b', 'c', 'd', 'e', 'f']

四:正则表达式常见问题

4.1 常见错误及解决方案

4.1.1 转义符使用错误

  • 问题:Python 中直接写 '\d',导致 \d 被当作转义符(实际匹配 d);
  • 解决方案:使用原始字符串 r'\d' 或双转义 '\\d'。

4.1.2 贪婪匹配导致的过度匹配

  • 问题:用 .* 匹配时,匹配范围超出预期(如 `>.* 匹配整个字符串);
  • 解决方案:使用非贪婪量词 .*? 或更精准的字符集(如 [^<]*)。

4.1.3 分组捕获与非捕获混淆

  • 问题:使用 findall() 时,正则中有分组,导致返回分组内容而非整体匹配;
  • 解决方案:无需捕获时用非捕获分组 (?:),或调整正则结构(如 r'(\d)\1+' 改为 r'\d\1+',但需注意反向引用的有效性)。

4.1.4 断言的位置错误

  • 问题:将断言写在匹配内容后面,导致匹配失败(如 \d+(?=元) 写成 (?=元)\d+,想匹配 "元" 后面的数字,实际匹配 "元" 前面的数字);
  • 解决方案:明确断言的作用(先行断言在后面,后行断言在前面)。

4.2 跨语言兼容性注意事项

  1. 转义规则:
  • Python/Java:字符串中需双转义 \\d,或用原始字符串 / 正则字面量;
  • JS/Perl:正则字面量中直接写 \d,无需转义。
  1. 修饰符差异:
  • Python 无 g 修饰符,通过 findall()/finditer() 实现全局匹配;
  • Java 无 g 修饰符,通过循环 Matcher.find() 实现全局匹配。
  1. 扩展特性支持:
  • 后行断言:Python 3.7+、JS ES2018+、Java 8+ 支持;
  • 命名分组:Python 支持 (?P<name>),JS/Java 支持 (?<name>);
  • Unicode 匹配:Python 需加 re.UNICODE,Java 默认支持,JS 需开启 u 修饰符(/pattern/u)。

4.3 正则调试工具推荐

  1. 在线调试工具:
  • Regex101:支持多语言(Python/JS/Java 等),实时显示匹配过程和结果;
  • Regexr:可视化正则匹配,支持语法提示和示例库;
  • 菜鸟教程正则测试工具:适合新手,简洁易用。
  1. 本地工具:
  • Sublime Text/VS Code:内置正则匹配功能,支持实时预览;
  • Python IDLE:通过 re.findall()/re.search() 快速测试正则。

4.4正则表达式的优先级

下表清晰列出正则核心语法的优先级顺序,优先级越高的元素越先被解析:

优先级 语法元素 核心说明
1(最高) 转义字符(\) 将特殊字符转为普通字符(如。匹配点、* 匹配星号)
2 分组 / 捕获(()) 改变执行顺序,支持捕获组、非捕获组、命名组等
3 量词(重复限定符) *、+、?、{n}、{n,}、{n,m}(含非贪婪修饰符?)
4 锚点 / 边界 ^(行首)、$(行尾)、\b(单词边界)、\A/\Z(字符串首尾)
5 字符类 [](字符集合)、[^](否定集合)、\d/\w/\s(预定义字符类)
6 普通字符 无特殊含义的字母、数字、符号(如 a、5、-)
7(最低) 管道符(|) 逻辑 "或",匹配任意一个分支

第五部分:正则表达式总结与进阶学习资源

5.1 核心知识点总结

正则表达式的学习路径可概括为:

  1. 基础层:元字符 → 字符集 → 量词 → 分组与选择 → 修饰符;
  1. 进阶层:反向引用 → 断言 → 条件匹配 → Unicode 匹配;
  1. 实战层:数据提取 → 格式校验 → 文本替换 → 字符串分割。

核心原则:精准匹配 (用最具体的规则匹配目标内容)、性能优先 (避免过度回溯和宽泛模式)、可读性优先(复杂正则加注释或拆分)。

5.2 进阶学习资源

  1. 在线文档:

2.实战练习:

结语

正则表达式是一门 "看似复杂,实则简洁" 的语言,掌握它的核心是 "理解模式匹配的逻辑" 而非死记硬背语法。初期可以从简单场景入手(如提取数字、验证手机号),逐步过渡到复杂场景(如断言、条件匹配),配合调试工具和实战练习,很快就能熟练运用。

相关推荐
sc.溯琛3 小时前
MySQL 进阶实验:数据库与数据表管理完全指南
数据库·oracle
利刃大大3 小时前
【JavaSE】Stream API && Optiona类 && 正则表达式
正则表达式
好记忆不如烂笔头abc3 小时前
oracle迁移到sqlserver的注意点
数据库·oracle·sqlserver
YJlio3 小时前
ZoomIt 学习笔记(11.11):休息计时器与演讲节奏控制——倒计时、番茄钟与现场掌控力
数据库·笔记·学习
山土成旧客3 小时前
【Python学习打卡-Day23】从重复到重用:用Pipeline和ColumnTransformer重构你的机器学习工作流
python·学习·重构
武子康3 小时前
Java-202 RabbitMQ 生产安装与容器快速启动:Erlang 兼容、RPM 部署与常用命令
java·消息队列·rabbitmq·erlang·java-rabbitmq·mq
棒棒的皮皮3 小时前
【OpenCV】Python图像处理之平滑处理
图像处理·python·opencv·计算机视觉
a程序小傲4 小时前
米哈游Java后端面试被问:Spring Boot Starter的制作原理
java·spring boot·后端
Kurbaneli4 小时前
Python列表推导式保姆级教程
python