1
2
3
4
5
6
7
作者:李晓辉

联系方式:

1. 微信:Lxh_Chat

2. 邮箱:939958092@qq.com
  1. Python 系列(一) 从零开始认识 Python,入门其实没那么难
  2. Python 系列(二) 学 Python 的第一步,把开发环境搭建好
  3. Python 系列(三) 基础语法避坑指南,缩进、大小写、续行与注释
  4. Python 系列(四) Python 输入输出详解,print()、input() 与常见避坑指南
  5. Python 系列(五) 数值类型与运算符详解,整除、浮点精度与进制转换实战
  6. Python 系列(六) 字符串处理实战,查找、替换、切片与日志分析
  7. Python 系列(七) 类型转换详解,int()、float()、str() 与金额计算避坑
  8. Python 系列(八) Python 流程控制入门,掌握 if、elif 和 else
  9. Python 系列(九) Python 逻辑运算符详解,and、or、not 一篇搞懂
  10. Python 系列(十) while、for 循环实战,彻底搞懂 range、break 与 continue
  11. Python 系列(十一) list、tuple、set、dict,到底该怎么选?
  12. Python 系列(十二) List 列表实战,搞定数据增删改查与批量处理
  13. Python 系列(十三) Tuple 元组实战,搞懂不可变特性与 enumerate() 遍历
  14. Python 系列(十四) Set 集合实战,搞定数据去重与集合运算
  15. Python 系列(十五) Dict 字典详解,掌握快速查找与键值操作
  16. Python 系列(十六) sort() 和 sorted() 详解,掌握数据排序技巧
  17. Python 系列(十七) 自定义函数入门,让重复代码一次编写多次使用
  18. Python 系列(十八) 函数参数详解,搞懂位置参数、关键字参数和默认参数
  19. Python 系列(十九) 从 *args 到 **kwargs,让函数调用更灵活
  20. Python 系列(二十) 变量作用域是什么?一文搞懂 global 和 nonlocal
  21. Python 系列(二十一) 搞懂变量、对象和引用,理解函数参数传递机制
  22. Python 系列(二十二) map() 和 filter() 详解,轻松实现批量数据处理与筛选
  23. Python 系列(二十三) 嵌套函数与 lambda 表达式详解,搞懂作用域与闭包
  24. Python 系列(二十四) 搞懂递归函数,从基线条件到实际应用
  25. Python 系列(二十五) 聊聊模块与 import,让代码告别重复编写
  26. Python 系列(二十六) Python 模块入门,从 import 到模块复用
  27. Python 系列(二十七) Python dir() 函数详解,快速查看模块中的函数与属性
  28. Python 系列(二十八) 不会用 Python 标准库?教你快速查找模块和函数
  29. Python 系列(二十九) 搞懂 Python sys 模块,让脚本更适合自动化运维
  30. Python 系列(三十) 为什么 0.1 + 0.2 不等于 0.3?彻底搞懂 Decimal
  31. Python 系列(三十一) 搞懂 datetime 日期时间,轻松处理时区与时间计算
  32. Python 系列(三十二) 面向对象编程入门,从零理解类、对象、封装与继承
  33. Python 系列(三十三) 什么是类和对象?零基础理解 Python class
  34. Python 系列(三十四) Python 属性详解,搞懂类属性、实例属性和 setter
  35. Python 系列(三十五) 学会特殊方法,让对象打印出你想要的内容
  36. Python 系列(三十六) 多个对象如何共享数据?彻底搞懂类变量
  37. Python 系列(三十七) 搞懂父类、子类和方法重写,真正理解继承与多态
  38. Python 系列(三十八) 如何判断数据类型?三个内置函数一次讲明白
  39. Python 系列(三十九) 搞懂异常模型与常见异常类型,读懂程序报错
  40. Python 系列(四十) 从零学会异常处理,掌握 try、except、else 和 finally
  41. Python 系列(四十一) ValueError、TypeError 到底是什么?一文搞懂常见异常
  42. Python 系列(四十二) ValueError、TypeError 到底是什么?一文搞懂常见异常
  43. Python 系列(四十三) 别只会处理异常!学会自定义异常,让程序更容易维护
  44. Python 系列(四十四) 从零认识 assert 断言,让程序学会检查自己
  45. Python 系列(四十五) 使用 sys 模块处理标准输入与输出
  46. Python 系列(四十六) 使用 open() 读取和写入文件
  47. Python 系列(四十七) 学会读写文本文件,让程序保存运行结果
  48. Python 系列(四十八) 搞懂 bytes、bytearray 和二进制读写
  49. Python 系列(四十九) 使用 seek() 和 tell() 灵活控制文件读取位置
  50. Python 系列(五十) 掌握 os 和 os.path,实现文件与目录自动化管理
  51. Python 系列(五十一) 掌握列表推导式,轻松实现列表生成与数据筛选
  52. Python 系列(五十二) 掌握字典推导式,让字典创建更简单
  53. Python 系列(五十三) 从零理解生成器,轻松处理大量数据
  54. Python 系列(五十五) 使用 zip() 同时遍历多个集合
  55. Python 系列(五十六) 使用 re 模块进行正则表达式匹配
  56. Python 系列(五十七) 理解正则表达式的字符类和量词
  57. Python 系列(五十八) 搞懂正则表达式中的分组与量词
  58. Python 系列(五十九) 学会正则捕获组,轻松提取文本数据
  59. Python 系列(六十) 掌握 re 模块的常用函数
  60. Python 系列(六十一) JSON 是什么?语法规则、数据类型与校验方法
  61. Python 系列(六十二) 使用 pdb 逐步调试程序,定位代码问题
  62. Python 系列(六十三) 在交互式 Python shell 中启动调试器
  63. Python 系列(六十四) 从零开始学习 pdb 命令行调试
  64. Python 系列(六十五) 使用 pdb 设置断点并检查程序执行上下文

前面我们已经学习了 Python 的文件读写、列表推导式、字典推导式、生成器以及 zip() 等内容。

现在,假设你正在维护一台 Linux 服务器,需要检查 Nginx 日志,找出所有访问了某个接口的请求。

日志可能长这样:

1
2
3
4
192.168.1.10 - - [22/Oct/2026:10:15:30] "GET /index.html HTTP/1.1" 200
192.168.1.11 - - [22/Oct/2026:10:16:12] "GET /api/users HTTP/1.1" 200
192.168.1.12 - - [22/Oct/2026:10:17:45] "POST /api/login HTTP/1.1" 500
192.168.1.13 - - [22/Oct/2026:10:18:20] "GET /index.html HTTP/1.1" 200

如果只想查找 /api 相关请求,我们可以使用字符串的 in 操作符:

1
2
3
log = 'GET /api/users HTTP/1.1'

print('/api' in log)

执行结果:

1
True

这种方式可以检查字符串中是否包含指定内容。

但如果需求变得复杂呢?

  • 查找所有以 /api 开头的请求路径。
  • 找出日志中所有的 IP 地址。
  • 提取 HTTP 状态码为 500 的记录。
  • 查找包含数字的字符串。
  • 找出符合指定格式的邮箱地址。

如果每种需求都要自己编写一套字符串判断逻辑,代码很容易变得复杂。

有没有一种方法,能够先描述我们要查找的内容具有什么规律,再让 Python 按照这个规律进行匹配?

这就是正则表达式可以发挥作用的地方。

什么是正则表达式

正则表达式(Regular Expression,简称 Regex 或 RegEx)是一种用于描述字符串匹配规则的表达式。

听起来有些抽象,我们可以用一个简单的例子理解。

假设有下面三个字符串:

1
2
3
text1 = 'abc123'
text2 = 'abc456'
text3 = 'abcdef'

如果希望找到包含数字的字符串,我们可以直接检查每个字符串中是否出现数字。

但如果希望进一步要求字符串中必须出现连续的三个数字,手动判断就会麻烦一些。

正则表达式允许我们用一种专门的语法来描述这些规则。

例如:

1
pattern = r'\d{3}'

这里暂时不需要记住每个符号的含义,只需要知道:这个表达式描述的是连续出现三个数字的规则。

后续文章会详细讲解 \d、{3} 等符号。

可以先把正则表达式理解成一张用于查找内容的规则说明书。

Python 为什么需要 re 模块

Python 提供了内置的 re 模块,用于处理正则表达式。

使用之前,需要先导入模块:

1
import re

这行代码的意思是:让当前 Python 程序能够使用 re 模块提供的功能。

这里的 import 可以理解为引入一个工具箱。引入之后,我们就可以调用其中的方法。

例如:

1
2
3
4
5
import re

result = re.search("c", "abcdefg")

print(result)

执行结果类似:

1
<re.Match object; span=(2, 3), match='c'>

运行时看到的对象显示信息可能因 Python 版本等因素有所不同,但匹配结果是一致的。

我们来拆解这段代码:

1
re.search("c", "abcdefg")

其中:

  • re:刚才导入的模块。
  • search():在字符串中搜索符合规则的内容。
  • "c":要查找的内容,这里使用的是最简单的正则表达式。
  • "abcdefg":被搜索的字符串。

Python 从字符串中查找字符 c,找到了符合条件的内容,于是返回一个匹配对象。

这个对象不仅表示匹配成功,还保存了匹配内容以及它在字符串中的位置等信息。

注意,search() 返回的不是简单的 True 或 False。

如果没有找到匹配内容,它会返回 None。

理解正则表达式的匹配流程

在继续学习具体方法之前,我们先从整体上了解正则表达式是如何工作的。

假设我们希望从下面这段文本中提取数字:

1
text = "服务器 web01 的 CPU 使用率为 85%"

我们编写一条匹配规则:

1
pattern = r"\d+"

这条规则表示匹配一个或多个连续数字。

接下来,Python 会使用这条规则检查文本,并根据匹配方法返回结果。

整个过程可以用下面的流程图表示:

flowchart TD
    A["准备待检查的字符串"] --> B["编写正则表达式规则"]
    B --> C{"是否需要重复使用规则?"}
    C -->|是| D["re.compile() 编译规则"]
    C -->|否| E["直接调用 re.match()、re.search() 等方法"]
    D --> F["调用编译对象的匹配方法"]
    E --> G["执行正则表达式匹配"]
    F --> G
    G --> H{"是否找到匹配内容?"}
    H -->|是| I["获取匹配结果"]
    H -->|否| J["返回 None 或空列表"]
    I --> K["提取、显示或进一步处理数据"]

这张图展示的是正则表达式从规则定义到结果处理的大致过程。

需要注意,re.compile() 并不是每次匹配都必须执行的步骤。

如果只是简单地搜索一次内容,可以直接使用 re.search()。如果需要反复使用同一条规则,可以先使用 re.compile() 创建正则表达式对象。

另外,不同匹配方法的失败结果也有所不同:

  • re.match() 和 re.search():没有找到匹配内容时返回 None。
  • re.findall():没有找到匹配内容时返回空列表 []。

了解这一点之后,再学习具体方法就会更加容易。

第一次使用正则表达式

先从最简单的例子开始。

创建一个文件 re_demo.py,输入以下代码:

1
2
3
4
5
6
7
8
9
10
11
import re

text = "Python is useful"

result = re.search("Python", text)

if result:
print("找到了匹配内容")
print(result.group())
else:
print("没有找到匹配内容")

在终端执行:

1
python3 re_demo.py

执行结果:

1
2
找到了匹配内容
Python

这段代码完成了一个完整的搜索过程。

首先,程序将字符串 Python is useful 保存到变量 text 中。

接着,re.search() 在这个字符串中查找 Python。

如果找到了,返回匹配对象,并将对象保存到变量 result 中;如果没有找到,result 就是 None。

然后,程序通过 if result 判断搜索是否成功。

最后,result.group() 取出真正匹配到的文本。

这里需要认识一个新的方法:group()。

它的作用是获取匹配结果中的文本内容。对于我们目前学习的简单匹配,可以先把它理解为“取出找到的内容”。

使用 search 查找字符串

re.search() 是学习正则表达式时最常用的方法之一。

它会从字符串开头开始搜索,找到第一个符合规则的位置后,就返回匹配结果。

例如:

1
2
3
4
5
6
7
8
import re

text = "abc123def456"

result = re.search(r"\d+", text)

if result:
print(result.group())

执行结果:

1
123

这里的 r"\d+" 是一个正则表达式。

暂时只需要知道,它能够匹配一个或多个连续数字。后续文章会进一步讲解其中的符号。

字符串中有 123 和 456 两组数字,为什么只输出 123?

因为 search() 找到第一处符合条件的内容后,就会停止搜索并返回结果。

如果想找出所有符合条件的内容,就需要使用其他方法。

使用 findall 查找所有匹配内容

假设我们需要从一段文本中提取所有数字:

1
2
3
4
5
6
7
import re

text = "web01 使用了 80 个连接,web02 使用了 120 个连接"

result = re.findall(r"\d+", text)

print(result)

执行结果:

1
['01', '80', '02', '120']

这里的 findall() 会查找所有符合规则的内容,并将匹配结果放进一个列表中。

为什么结果是一个列表?

因为我们可能找到多个数字,而列表正好可以保存多个数据。

需要注意,findall() 返回的是字符串列表,而不是整数列表。如果之后需要进行数学计算,还需要根据实际情况使用 int() 等方法进行类型转换。

例如:

1
2
3
4
5
6
7
8
import re

text = "web01 使用了 80 个连接,web02 使用了 120 个连接"

numbers = re.findall(r"\d+", text)

for number in numbers:
print(int(number))

执行结果:

1
2
3
4
1
80
2
120

这里出现了一个容易让初学者困惑的细节:web01 中的 01 转换成整数后变成了 1。

这是因为整数不会保留数字开头的零。如果需要保留原始格式,就应该继续使用字符串,不要急着转换成整数。

使用 match 从字符串开头匹配

re.match() 也是用于匹配字符串的方法,但它与 search() 有一个重要区别。

match() 只尝试从字符串的开头进行匹配。

看下面的例子:

1
2
3
4
5
6
7
8
9
10
import re

text = "Python is useful"

result = re.match("Python", text)

if result:
print(result.group())
else:
print("没有找到匹配内容")

执行结果:

1
Python

因为字符串开头就是 Python,所以匹配成功。

再看一个例子:

1
2
3
4
5
6
7
import re

text = "I like Python"

result = re.match("Python", text)

print(result)

执行结果:

1
None

虽然字符串中确实存在 Python,但它不是从开头开始的,因此 match() 返回 None。

如果把同样的例子改成 search():

1
2
3
4
5
6
7
8
import re

text = "I like Python"

result = re.search("Python", text)

if result:
print(result.group())

执行结果:

1
Python

这次就能找到内容了。

对比三种常用方法

方法作用返回结果
re.match()从字符串开头尝试匹配匹配对象或 None
re.search()搜索第一处匹配内容匹配对象或 None
re.findall()查找所有匹配内容列表

可以使用下面的流程图理解它们的区别:

flowchart TD
    A["需要查找字符串"] --> B{"希望如何查找?"}
    B --> C["从字符串开头匹配"]
    B --> D["查找第一处匹配"]
    B --> E["查找所有匹配"]
    C --> F["re.match()"]
    D --> G["re.search()"]
    E --> H["re.findall()"]

实际工作中,不需要死记硬背所有方法。只要先明确自己需要从哪里开始查找,以及需要一个结果还是多个结果,就更容易选择合适的方法。

编译正则表达式:使用 re.compile()

前面我们一直采用这样的方式进行匹配:

1
2
3
import re

result = re.search(r"\d+", "服务器使用了 85% CPU")

这种写法很直接:把正则表达式和需要检查的字符串一起传给 re.search()。

但是,假设我们需要检查 100 条服务器日志,每条日志都要使用同一条规则查找数字,应该怎么办?

一种方式是反复调用 re.search(),每次都传入相同的正则表达式。

另一种方式是:先把正则表达式编译成一个可以重复使用的规则对象,再用这个对象检查不同的字符串。

Python 提供了 re.compile() 方法来完成这件事。

先理解什么是编译

在这里,可以先把“编译正则表达式”理解成:将我们写下的匹配规则交给 Python 处理,得到一个可以反复使用的正则表达式对象。

例如:

1
2
3
import re

pattern = re.compile(r"\d+")

这段代码做了两件事:

  • r"\d+" 是我们编写的正则表达式规则。
  • re.compile() 将这条规则处理成一个正则表达式对象,并保存到变量 pattern 中。

这里的 pattern 不再是普通的字符串,而是一个可以调用匹配方法的对象。

可以通过 type() 查看它的类型:

1
2
3
4
5
import re

pattern = re.compile(r"\d+")

print(type(pattern))

执行结果:

1
<class 're.Pattern'>

看到 re.Pattern,就说明 pattern 保存的是一个正则表达式对象。

注意,这里所说的编译是正则表达式的编译,不是把整个 Python 程序编译成可执行文件。

使用编译后的规则匹配字符串

得到正则表达式对象之后,应该如何使用?

我们可以直接调用对象提供的 search() 方法:

1
2
3
4
5
6
7
8
9
10
import re

pattern = re.compile(r"\d+")

text = "服务器 web01 的 CPU 使用率为 85%"

result = pattern.search(text)

if result:
print(result.group())

执行结果:

1
01

为什么输出的是 01,而不是 85?

因为 search() 返回的是第一处匹配结果。字符串中的 web01 已经包含数字,所以程序找到 01 后就停止搜索了。

如果我们只想提取 CPU 使用率,可以在后续学习字符类、量词等知识后,再编写更加精确的匹配规则。

这里最重要的是理解调用方式的变化。

直接使用模块的方法:

1
re.search(r"\d+", text)

先编译,再使用对象的方法:

1
2
3
pattern = re.compile(r"\d+")

pattern.search(text)

两种方式都可以完成匹配。区别在于,第二种方式把规则单独保存了下来,可以重复使用。

使用同一条规则检查多条日志

现在,我们把编译正则表达式的方式放到实际的运维场景中。

假设需要检查多条服务器日志,并提取每条日志中的第一组数字。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
import re

# 编译正则表达式,创建可以重复使用的规则对象
pattern = re.compile(r"\d+")

# 准备多条模拟日志
logs = [
"web01 CPU 使用率 75%",
"web02 CPU 使用率 82%",
"web03 CPU 使用率 91%"
]

# 使用同一条规则检查每一条日志
for log in logs:
result = pattern.search(log)

if result:
print(log, "匹配到的数字:", result.group())

执行结果:

1
2
3
web01 CPU 使用率 75% 匹配到的数字: 01
web02 CPU 使用率 82% 匹配到的数字: 02
web03 CPU 使用率 91% 匹配到的数字: 03

我们逐步理解这段程序。

首先,re.compile(r"\d+") 创建了一个正则表达式对象,并保存到 pattern 中。

接着,logs 列表保存了三条模拟日志。

然后,for 循环逐条取出日志,每次都调用 pattern.search(log)。

虽然检查了三条不同的日志,但使用的始终是同一个正则表达式对象,不需要在循环内部重新编写匹配规则。

这个例子展示了 re.compile() 的主要使用价值:将匹配规则和待检查的数据分开,让同一条规则可以在多个地方重复使用。

编译后的对象也能使用其他匹配方法

前面学习的 search()、match() 和 findall(),不仅可以通过 re 模块调用,也可以通过编译得到的正则表达式对象调用。

例如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import re

pattern = re.compile(r"\d+")

text = "web01 CPU 75%,web02 CPU 82%"

search_result = pattern.search(text)
match_result = pattern.match(text)
all_results = pattern.findall(text)

if search_result:
print("第一处匹配:", search_result.group())

print("开头匹配:", match_result)
print("所有匹配:", all_results)

执行结果:

1
2
3
第一处匹配: 01
开头匹配: None
所有匹配: ['01', '75', '02', '82']

这里需要特别注意:编译正则表达式不会改变匹配规则本身。

pattern.search() 仍然是搜索第一处匹配,pattern.match() 仍然只从字符串开头尝试匹配,pattern.findall() 仍然会返回所有匹配内容。

编译只是提供了另一种组织和使用正则表达式的方式。

re.compile() 一定更快吗

刚接触 re.compile() 时,很容易认为只要使用它,程序运行速度就一定会更快。

实际上,不应该这样理解。

当我们使用 re.search() 等方法时,Python 的 re 模块会缓存近期使用过的一些正则表达式,以减少重复处理规则的开销。因此,对于简单的匹配任务,直接调用模块方法通常已经足够。

显式使用 re.compile() 的主要优势是:

  • 规则可以重复使用: 不需要在多个位置反复编写同一条规则。
  • 代码更清晰: 可以使用 pattern 等变量名明确表示这是一条匹配规则。
  • 适合批量处理: 在循环中检查大量日志时,规则可以在循环外统一定义。
  • 便于维护: 当匹配规则发生变化时,可以集中修改规则定义。

当然,如果程序确实存在大量正则表达式匹配操作,性能是否改善还要结合实际代码进行测试。

对于初学者来说,先掌握如何定义和复用规则,比过早追求性能优化更加重要。

为什么正则表达式前面经常写字母 r

在前面的代码中,我们经常看到这样的写法:

1
pattern = r"\d+"

为什么字符串前面多了一个 r?

因为 Python 普通字符串中的反斜杠 \ 还可能用于表示特殊字符。

例如:

1
2
3
text = "hello\nworld"

print(text)

执行结果:

1
2
hello
world

这里的 \n 表示换行。

但是,正则表达式也大量使用反斜杠来表示特殊规则。如果每次都需要考虑 Python 字符串本身的转义规则,书写起来就容易混淆。

因此,正则表达式通常使用原始字符串:

1
pattern = r"\d+"

字符串前面的 r 表示原始字符串,能够让反斜杠按照原始字符串的规则保留下来,而不是把 \d 当作 Python 字符串中的转义序列处理。

对于初学者来说,可以先养成一个习惯:

编写正则表达式时,优先使用 r"..." 的形式。

这并不意味着所有正则表达式字符串都必须以 r 开头,而是这种写法通常更清晰,也更不容易遇到转义问题。

使用正则表达式提取 Linux 日志信息

前面我们提到过 Nginx 日志,现在尝试将正则表达式用于实际的运维场景。

假设我们拿到了一条简化后的访问日志:

1
log = '192.168.1.12 - - "POST /api/login HTTP/1.1" 500'

现在,我们想提取其中的 IP 地址和 HTTP 状态码。

可以先使用最简单的方式分别查找:

1
2
3
4
5
6
7
8
9
10
11
12
import re

log = '192.168.1.12 - - "POST /api/login HTTP/1.1" 500'

ip_result = re.search(r"\d+\.\d+\.\d+\.\d+", log)
status_result = re.search(r"\s(\d{3})$", log)

if ip_result:
print("客户端 IP:", ip_result.group())

if status_result:
print("HTTP 状态码:", status_result.group(1))

执行结果:

1
2
客户端 IP: 192.168.1.12
HTTP 状态码: 500

这里的正则表达式稍微复杂了一些,但可以先从整体理解。

r"\d+\.\d+\.\d+\.\d+" 用于查找由四段数字组成、段与段之间使用点号分隔的内容。

需要注意,这只是一个简化的 IP 地址匹配规则,并不能严格验证每一段是否处于合法范围内。

r"\s(\d{3})$" 用于查找字符串末尾的三位状态码,并通过括号保存其中的匹配内容。

这里使用了 group(1),而不是 group()。

  • group() 或 group(0):获取整个匹配结果。
  • group(1):获取第一个括号捕获的内容。

例如,整个匹配可能包含状态码前面的空格,但 group(1) 只会返回 500。

括号、\s、\d 和 $ 的具体含义会在后续内容中逐步学习,现在不必一次全部记住。

还需要注意,这个例子假设日志末尾就是状态码。如果日志格式不同,就需要相应调整匹配规则。

初学正则表达式时需要注意什么

正则表达式很灵活,但它并不是越复杂越好。

刚开始学习时,建议注意以下几点。

不要把匹配对象当成字符串。

例如:

1
2
3
4
5
6
import re

result = re.search("Python", "I like Python")

print(result)
print(result.group())

第一行输出的是匹配对象的信息,第二行才是匹配到的字符串。

注意匹配失败的情况。

如果没有匹配到内容,search() 和 match() 都会返回 None。直接调用 result.group() 就可能产生 AttributeError 异常。

所以,在获取匹配内容之前,应该先检查是否匹配成功。

不要认为匹配到了就代表数据一定合法。

例如,简单的 IP 地址正则表达式可能匹配到 999.999.999.999 这样的内容。因此,格式匹配和数据合法性校验是两个不同的问题。

对于复杂的数据校验,应该根据实际需求选择更可靠的验证方式。

理解编译规则与匹配数据的区别。

re.compile() 负责创建正则表达式对象,而 pattern.search(text) 等方法负责使用规则检查具体的字符串。

规则可以重复使用,但每次匹配的结果取决于传入的字符串。

动手练习

现在通过几个小练习检查自己是否理解了今天的内容。

练习:查找字符串中的数字

1
2
3
4
5
6
7
import re

text = "服务器 web01 的 CPU 使用率为 85%"

result = re.findall(r"\d+", text)

print(result)

请先自己猜测输出结果,再运行程序验证。

参考输出:

1
['01', '85']

练习:查找第一个匹配内容

1
2
3
4
5
6
7
8
import re

text = "Linux Python Linux Python"

result = re.search("Python", text)

if result:
print(result.group())

参考输出:

1
Python

虽然字符串中出现了两次 Python,但 search() 只返回第一处匹配结果。

1
2
3
4
5
6
import re

text = "Hello Python"

print(re.match("Python", text))
print(re.search("Python", text))

参考输出:

1
2
None
<re.Match object; ...>

第二行中匹配对象的具体显示内容会因 Python 版本等因素有所不同。

思考一下:为什么同一个字符串,使用两种方法会得到不同结果?

原因就在于 match() 只从字符串开头尝试匹配,而 search() 会在整个字符串中查找。

练习:使用 compile 重复利用规则

创建一个文件 re_compile_demo.py,输入以下代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
import re

# 编译一条用于查找数字的规则
pattern = re.compile(r"\d+")

# 准备多条需要检查的文本
texts = [
"web01",
"CPU 使用率 85%",
"当前有 120 个连接"
]

# 使用同一条规则逐个检查
for text in texts:
result = pattern.search(text)

if result:
print(text, "->", result.group())
else:
print(text, "-> 没有找到数字")

执行程序:

1
python3 re_compile_demo.py

参考输出:

1
2
3
web01 -> 01
CPU 使用率 85% -> 85
当前有 120 个连接 -> 120

思考以下问题:

  • pattern 保存的是什么?
  • 为什么不需要在循环中反复调用 re.compile()?
  • 如果把 pattern.search(text) 改成 re.search(r"\d+", text),还能得到匹配结果吗?

答案分别是:pattern 保存编译后的正则表达式对象;同一条规则可以重复使用;直接调用 re.search() 也能完成匹配。

写在最后

本文学习了 Python 内置的 re 模块,了解了正则表达式的基本用途,以及如何使用 match()、search() 和 findall() 查找字符串中的内容。

此外,我们还学习了 re.compile(),知道如何将匹配规则编译成正则表达式对象,并在多条日志或多段文本中重复使用。

通过正则匹配流程图,我们梳理了从编写规则、选择匹配方法,到判断结果和提取内容的基本过程。

最后,我们尝试从 Linux 运维日志中提取客户端 IP 地址和 HTTP 状态码,初步体验了正则表达式在自动化运维中的实际价值。

不过,目前我们只是使用了一些基础匹配规则,还没有系统学习正则表达式中的各种符号。

下一篇将学习正则表达式中的字符类与量词,逐步理解如何匹配数字、字母、指定字符,以及如何规定某种字符出现一次、几次或任意多次。

掌握这些基础之后,我们就能够编写更加灵活的匹配规则,从文本和日志中提取需要的信息。