正则表达式入门教程:从零开始掌握文本匹配的艺术
你有没有遇到过这样的场景?需要从一段长长的日志中提取所有错误代码,或者验证用户输入的邮箱是否合法,又或者批量替换文本中的某个模式。这些看似琐碎的任务,其实都可以通过一种强大的工具轻松解决——正则表达式。
正则表达式,简称 regex,是程序员处理文本时的“瑞士军刀”。它不是一门编程语言,而是一种模式描述语言,用来定义字符串的匹配规则。无论你是初学者还是有一定经验的开发者,只要掌握它的基本语法,就能大幅提升文本处理效率。
本文将带你从零开始,逐步构建对正则表达式的理解。我们将从最基础的字符匹配讲起,逐步深入到分组、捕获、替换等高级用法,并结合真实项目案例,让你真正“会用”而不是“背规则”。
什么是正则表达式?它能做什么?
简单来说,正则表达式就是一种用来描述字符串模式的表达式。你可以把它想象成一个“智能搜索模板”:不是单纯找某个固定的字,而是告诉系统“我想要找什么样的结构”。
举个例子:
你有一份用户注册数据,其中包含邮箱字段。你想找出所有格式正确的邮箱地址。手动一个个检查显然不现实,但用正则表达式,只需一行代码就能完成。
在 Python 中,我们可以这样写:
import re
email_pattern = r'^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$'
test_emails = [
"user@example.com",
"admin@company.org",
"invalid.email",
"test@sub.domain.co.uk"
]
for email in test_emails:
if re.match(email_pattern, email):
print(f"✅ {email} 是合法邮箱")
else:
print(f"❌ {email} 格式不正确")
注释说明:
r'^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$'是正则表达式模式^表示字符串开始,$表示字符串结束,确保整个字符串都符合规则[a-zA-Z0-9._%+-]+匹配用户名部分,允许字母、数字和常见特殊字符@是必须存在的符号[a-zA-Z0-9.-]+匹配域名部分\.匹配点号(因为点在正则中有特殊含义,需转义){2,}表示至少两个字母,用于匹配顶级域名如.com、.orgre.match()从头开始匹配,返回匹配对象或 None
基础字符匹配:从最简单的开始
正则表达式的核心是“匹配”。我们先从最基础的字符匹配入手。
字面量匹配
最简单的正则就是直接写你要找的字符。比如,要找字符串中的 "hello":
import re
text = "Hello world, this is a hello test."
match = re.search(r'hello', text, re.IGNORECASE)
if match:
print(f"找到匹配项:{match.group()}")
else:
print("未找到匹配")
注释说明:
r'hello'是正则表达式,表示精确匹配字符 "hello"re.IGNORECASE参数让匹配不区分大小写,因此 "Hello" 也能被匹配到re.search()在整个字符串中查找第一个匹配项,返回匹配对象.group()提取实际匹配到的文本
特殊字符与转义
正则表达式中有一些字符具有特殊含义,比如 .、^、$、*、+、? 等。如果你真的想匹配这些字符本身,就需要用反斜杠 \ 转义。
例如,要匹配实际的点号:
import re
text = "文件名:data.json"
pattern = r'\.'
match = re.search(pattern, text)
if match:
print(f"找到了点号:{match.group()}")
注释说明:
\.表示匹配一个字面量的点号- 若不转义,
.会匹配任意单个字符,导致错误匹配
元字符与量词:掌握匹配的“节奏”
元字符是正则表达式中的“控制键”,它们决定了匹配的行为和范围。
常见元字符速查表
| 元字符 | 含义 | 示例 |
|---|---|---|
. |
匹配任意单个字符(换行符除外) | a.c 可匹配 "abc"、"a2c" |
^ |
匹配字符串开头 | ^Hello 只匹配以 "Hello" 开头的字符串 |
$ |
匹配字符串结尾 | world$ 只匹配以 "world" 结尾的字符串 |
* |
匹配前一个字符 0 次或多次 | ab*c 可匹配 "ac"、"abc"、"abbc" |
+ |
匹配前一个字符 1 次或多次 | ab+c 可匹配 "abc"、"abbc",但不匹配 "ac" |
? |
匹配前一个字符 0 次或 1 次 | colou?r 可匹配 "color" 或 "colour" |
{n} |
精确匹配 n 次 | a{3} 只匹配 "aaa" |
{n,m} |
匹配 n 到 m 次 | a{2,4} 可匹配 "aa"、"aaa"、"aaaa" |
实际案例:手机号验证
我们来写一个正则表达式,验证中国大陆手机号格式(11位数字,以 1 开头,第二位为 3-9):
import re
phone_pattern = r'^1[3-9]\d{9}$'
test_phones = [
"13812345678", # 合法
"12345678901", # 非法(第二位不是3-9)
"1381234567", # 非法(不足11位)
"18912345678" # 合法
]
for phone in test_phones:
if re.match(phone_pattern, phone):
print(f"✅ {phone} 是有效手机号")
else:
print(f"❌ {phone} 格式错误")
注释说明:
^1[3-9]表示以 1 开头,第二位是 3 到 9 的数字\d{9}表示后面必须有 9 个数字$确保字符串结束,防止多出字符- 整体保证是 11 位合法手机号
字符类与预定义字符集:快速匹配一组字符
当你需要匹配多个可能的字符时,使用字符类(Character Class)会更高效。
字符类语法
[abc]:匹配 a、b 或 c 中任意一个[a-z]:匹配任意小写字母[A-Z]:匹配任意大写字母[0-9]:匹配任意数字[^abc]:匹配非 a、b、c 的字符(取反)
实例:提取数字
假设你有一段文本,需要提取所有数字:
import re
text = "订单号:20240512,金额:128.50元,发货时间:2024-05-13"
numbers = re.findall(r'\d+', text)
print(f"提取到的数字:{numbers}")
注释说明:
\d是预定义字符类,等价于[0-9]+表示一个或多个数字re.findall()返回所有匹配项组成的列表,无需循环
分组与捕获:让正则更智能
分组是正则表达式中非常强大的功能。通过括号 (),你可以将一部分表达式当作一个整体处理。
基本分组
import re
text = "张三,电话:13812345678,邮箱:zhangsan@example.com"
pattern = r'电话:(\d{11}),邮箱:([a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,})'
match = re.search(pattern, text)
if match:
phone = match.group(1) # 第一个分组:电话
email = match.group(2) # 第二个分组:邮箱
print(f"电话:{phone}")
print(f"邮箱:{email}")
注释说明:
(\d{11})是第一个分组,用于捕获手机号([a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,})是第二个分组,用于捕获邮箱group(1)和group(2)分别获取两个分组的内容- 分组编号从 1 开始,
group(0)是完整匹配的文本
替换与高级技巧:从匹配到处理
正则表达式不仅用于查找,还能用于替换。re.sub() 函数可以实现文本替换。
案例:清洗日志中的时间戳
日志中时间戳格式为 [2024-05-13 12:34:56],我们想将其替换为更简洁的格式:
import re
log_line = "[2024-05-13 12:34:56] 用户登录失败,IP:192.168.1.1"
cleaned = re.sub(r'\[\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}\]', '【时间】', log_line)
print(cleaned)
注释说明:
\d{4}匹配4位年份\d{2}匹配两位月、日、时、分、秒- 整个时间戳被
[]包裹,用\[\]转义re.sub()第一个参数是正则模式,第二个是替换内容,第三个是原字符串
总结:正则表达式入门教程的最后建议
正则表达式虽然强大,但初学者容易陷入“写得太复杂”或“误匹配”的陷阱。记住几个核心原则:
- 从小处着手:先从简单匹配开始,逐步叠加规则。
- 善用工具:使用在线正则测试工具(如 regex101.com)验证你的表达式。
- 测试多场景:确保边界情况(如空值、极端长度)也能正确处理。
- 保持可读性:复杂正则可用
re.VERBOSE模式拆分行注释。
掌握正则表达式,就像学会了“文本世界的语言”。它能让你在日志分析、数据清洗、表单验证等场景中游刃有余。希望这篇正则表达式入门教程,能成为你通往高效编程之路的第一步。