跳转到内容

正则表达式

正则表达式(Regular Expressions,也叫 REs、regexes 或 regex patterns)是一种用于匹配字符串中字符模式的强大工具,广泛应用于搜索、替换、验证文本等任务中,其理论基础源自形式语言理论中的正规语言和有限状态自动机

正则表达式由两类字符组成:

  1. 普通字符:如字母 a-z、数字 0-9,它们表示直接匹配自身
  2. 元字符:具有特殊含义的字符,如 .、^、* 等,用于控制匹配规则

正则表达式所作用的字符串取决于所用的工具,例如:

  • 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] 匹配任意数字
元字符 功能 等价于
\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、playing
  • ing$ 只会匹配:playing

POSIX 字符类是 POSIX(Portable Operating System Interface)标准定义的一组正则表达式字符集合,用于匹配特定类型的字符

为什么要有 POSIX 字符类?

  1. 标准化、跨平台:在 Unix 系统中,grep、awk、sed、lex 等工具广泛使用 POSIX 正则,POSIX 类的写法可读性强、语言无关、国际化友好
  2. 更具表达力:例如 [:alpha:] 明确表示“字母”,而不是依赖具体编码排序(如 [a-zA-Z])
  3. 更适合多语言环境: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

默认情况下 *、+、{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(尽可能少)
函数 功能 示例代码
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.