正则表达式
正则表达式(Regular Expressions,也叫 REs、regexes 或 regex patterns)是一种用于匹配字符串中字符模式的强大工具,广泛应用于搜索、替换、验证文本等任务中,其理论基础源自形式语言理论中的正规语言和有限状态自动机
正则表达式由两类字符组成:
- 普通字符:如字母
a-z、数字0-9,它们表示直接匹配自身 - 元字符:具有特殊含义的字符,如
.、^、*等,用于控制匹配规则
正则表达式所作用的字符串取决于所用的工具,例如:
- grep、sed 按“行”分割文本,那么一行就是一个字符串
- Python 的
re模块中,如果不启用多行模式(re.MULTILINE),会把整个文本当作字符串
| 元字符 | 功能 | 示例 |
|---|---|---|
. |
匹配任意单个字符(包括空格、标点等非单词字符,换行符除外) | a.c 可以匹配 abc、a1c、… |
^ |
匹配字符串开头 | ^abc 只能匹配以 abc 开头的字符串 |
$ |
匹配字符串结尾 | xyz$ 只能匹配以 xyz 结尾的字符串 |
* |
匹配前面的字符 0 次或多次(克林闭包) | a* 匹配空串、a、aa、… |
+ |
匹配前面的字符 1 次或多次(正闭包) | a+ 匹配 a、aa、… |
? |
匹配前面的字符 0 次或 1 次 | a? 匹配空串或者一个 a |
| |
匹配它左边或右边的任意一个模式 | cat|dog 匹配字符串中出现的 cat 或 dog |
| 元字符 | 功能 | 示例 |
|---|---|---|
{n} |
匹配前面的字符恰好 n 次 | a{3} 匹配 aaa |
{n,} |
匹配前面的字符至少 n 次 | a{2,} 匹配 aa、aaa、… |
{n,m} |
匹配前面的字符 n 到 m 次 | a{2,4} 匹配 aa、aaa、aaaa |
| 元字符 | 功能 | 示例 |
|---|---|---|
[] |
匹配括号内任意字符 | [abc] 匹配 a、b 或 c |
[^] |
匹配不在括号内的任意字符 | [^abc] 匹配除了 a、b、c 的其他字符 |
[a-z] |
匹配指定范围的字母字符 | [a-z] 匹配任意小写字母 |
[0-9] |
匹配指定范围内的数字字符 | [0-9] 匹配任意数字 |
预定义字符类
Section titled “预定义字符类”| 元字符 | 功能 | 等价于 |
|---|---|---|
\d |
匹配任意数字 | [0-9] |
\D |
匹配任意非数字 | [^0-9] |
\w |
匹配单词(字母、数字、下划线) | [a-zA-Z0-9_] |
\W |
匹配非单词 | [^a-zA-Z0-9_] |
\s |
匹配空白字符(空格、制表符等) | |
\S |
匹配非空白字符 |
| 元字符 | 功能 | 示例 |
|---|---|---|
() |
把多个字符/模式视为一个整体(分组) | (ab)+ 匹配 ab、abab、… |
(?:) |
非捕获组,仅用于分组不捕获 | (?:ab)+ 匹配同上,但不捕获 ab |
\n |
引用第 n 个捕获组的内容 | (\w+)\s+\1 匹配 hello hello(重复单词) |
什么是分组?
() 可以定义一个分组
什么是捕获?
当正则表达式中使用圆括号 () 定义一个分组时,匹配到的内容会被自动保存到一个编号的捕获组中,编号从 1 开始(组 0 通常表示整个匹配)
| 元字符 | 功能 | 示例 |
|---|---|---|
\ |
转义特殊字符 | \. 匹配 .;\* 匹配 * |
需要转义才能匹配自身的特殊字符:. ^ $ * + ? { } [ ] ( ) \ |
| 元字符 | 功能 | 示例 |
|---|---|---|
\b |
单词边界,仅在单词的开头或结尾处匹配 | \bcat\b 匹配完整的单词 cat |
\B |
非单词边界,匹配单词内部的位置 | \Bcat\B 匹配 cat,但 cat 必须嵌在其他单词中间 |
注意
\b和$不同:
$匹配的是“行尾位置”\b匹配的是“单词边界”对于文本
doing something playing:
ing\b会匹配:doing、something、playinging$只会匹配:playing
POSIX 字符类
Section titled “POSIX 字符类”POSIX 字符类是 POSIX(Portable Operating System Interface)标准定义的一组正则表达式字符集合,用于匹配特定类型的字符
为什么要有 POSIX 字符类?
- 标准化、跨平台:在 Unix 系统中,
grep、awk、sed、lex等工具广泛使用 POSIX 正则,POSIX 类的写法可读性强、语言无关、国际化友好 - 更具表达力:例如
[:alpha:]明确表示“字母”,而不是依赖具体编码排序(如[a-zA-Z]) - 更适合多语言环境:POSIX 字符类配合本地化(locale)支持,可适应不同国家字符集。
[a-zA-Z]只能表示 ASCII 英文字母,而[:alpha:]还可以匹配俄文字母、希腊字母等
语法格式:[:类名:]
| POSIX 字符类 | 含义 | 等价于(大致) |
|---|---|---|
[:digit:] |
匹配任意数字(0-9) | \d 或 [0-9] |
[:upper:] |
匹配任意大写字母(A-Z) | [A-Z] |
[:lower:] |
匹配任意小写字母(a-z) | [a-z] |
[:alpha:] |
匹配任意字母(a-z, A-Z) | [a-zA-Z] |
[:alnum:] |
匹配任意字母或数字 | [a-zA-Z0-9] |
[:space:] |
匹配任意空白字符(空格、制表符等) | \s |
[:blank:] |
匹配水平空白(空格、制表符) | [ \t] |
[:punct:] |
匹配任意标点符号 | |
[:word:] |
匹配任意单词字符(字母、数字、下划线) | \w 或 [a-zA-Z0-9_] |
[:cntrl:] |
匹配控制字符(如换页、回车) | [\x00-\x1F\x7F] |
[:graph:] |
匹配可见字符(非空格、非控制字符) | [[:alnum:][:punct:]] |
[:print:] |
匹配可打印字符(包括空格) | [[:graph:] ] |
[:xdigit:] |
匹配十六进制数字 | [0-9a-fA-F] |
注意:
- POSIX 字符类大小写敏感,例如
[:upper:]和[:UPPER:]不等价 - 它们通常在字符集
[]内使用,可以与其他字符组合,例如:[[:digit:]a-z]匹配数字或小写字母[[:alnum:]_]匹配字母、数字或下划线,等价于\w
贪婪与非贪婪模式
Section titled “贪婪与非贪婪模式”默认情况下 *、+、{n,m} 是贪婪模式,匹配尽可能多的字符;添加 ? 变成非贪婪模式,匹配尽可能少的字符。
字符串:
<tag>content</tag><tag>more</tag>
- 贪婪:
<tag>.*</tag>-> 匹配<tag>content</tag><tag>more</tag>(尽可能多)- 非贪婪:
<tag>.*?</tag>-> 只匹配<tag>content</tag>(尽可能少)字符串:
aaaa
- 贪婪:
a+-> 匹配aaaa(尽可能多)- 非贪婪:
a+?-> 只匹配a(尽可能少)
Python re 模块常用函数
Section titled “Python re 模块常用函数”| 函数 | 功能 | 示例代码 |
|---|---|---|
re.match() |
只匹配字符串开头,返回匹配对象或 None | re.match(r"\d+", "123abc") 返回 123 |
re.search() |
搜索整个字符串,返回第一个匹配对象或 None | re.search(r"\d+", "abc123def") 返回 123 |
re.fullmatch() |
要求整个字符串完全匹配模式,返回匹配对象或 None | re.fullmatch(r"\d+", "123") 返回 123,但 "123abc" 失败 |
re.findall() |
返回所有非重叠匹配的列表,捕获组返回元组列表 | re.findall(r"(\w+)=(\d+)", "id=123 name=456") 返回 [('id', '123'), ('name', '456')] |
re.finditer() |
返回所有非重叠匹配的迭代器,每个匹配是 Match 对象 | for m in re.finditer(r"\d+", "123 456") 遍历匹配 |
re.sub() |
替换所有匹配的部分,返回新字符串 | re.sub(r"\d+", "XXX", "id=123") 返回 id=XXX |
re.subn() |
类似 re.sub,但返回 (新字符串, 替换次数) 的元组 |
re.subn(r"\d+", "XXX", "id=123") 返回 ('id=XXX', 1) |
re.split() |
用模式分割字符串,返回列表 | re.split(r"\s+", "a b c") 返回 ['a', 'b', 'c'] |
注意:Python 的
re模块不直接支持 POSIX 类写法,推荐使用\d、\w、\s等字符类别。
捕获 – 重复匹配机制
- 正则式:
(ab)+ - 字符串:
ababab - 整体匹配(捕获组 0):
ababab - 捕获组 1:最后一个
ab
import re
pattern = r"(ab)+"text = "ababab"
match = re.search(pattern, text)if match: print("整体匹配(捕获组 0):", match.group(0)) print("捕获组 1:", match.group(1))整体匹配(捕获组 0):ababab捕获组 1:ab捕获 – 单次与多次匹配
- 正则式:
(\w+)=(\d+) - 字符串:
id=123 name=456 - 单次匹配(
re.search)只返回第一个匹配(id=123) - 多次匹配(
re.findall)返回所有匹配:[('id', '123'), ('name', '456')]
import re
pattern = r"(\w+)=(\d+)"text = "id=123 name=456"
# 单次匹配match = re.search(pattern, text)if match: print("整体匹配(捕获组 0):", match.group(0)) print("捕获组 1:", match.group(1)) print("捕获组 2:", match.group(2))
# 多次匹配matches = re.findall(pattern, text)for i, (group1, group2) in enumerate(matches, 1): print(f"第 {i} 次匹配:捕获组 1={group1},捕获组 2={group2}")引用 – 用于匹配重复单词
import re
text = "hello hello world"pattern = r'\b(\w+)\s+\1\b'
match = re.search(pattern, text)if match: print("重复单词:", match.group(0)) # hello hello print("被引用的内容(group 1):", match.group(1)) # hello引用 – 用于替换
import re
text = 'He said "hello" and then "bye".'pattern = r'"(.*?)"'
replaced = re.sub(pattern, r'\1', text)print("原始:", text)print("替换后:", replaced)# 替换后: He said hello and then bye.