使用元字符
4.1 再谈转义
元字符是一些在正则表达式里有着特殊含义的字符。英文句号.
是一个元字符,它可以匹配任意单个字符。左方括号 [
也是一个元字符,它标志着一个字符集合的开始。因为元字符有着特殊的含义,所以这些字符就无法用来代表它们本身。比如说,你不能使用 [
来匹配 [
本身,也不能使用 .
来匹配字符 .
。
来看一个例子,使用一个正则表达式去匹配一个包含 [
和 ]
字符的 JavaScript 数组。
import re
# 测试文本
text = """
var myArray = new Array();
...
if (myArray[0] == 0) {
}
...
"""
# 正则表达式
regexp = r'myArray[0]'
# 编译
pattern = re.compile(regexp)
# 匹配
m = pattern.search(text)
if m:
print(m.group(0))
else:
print("No match!")
这里通过使用正则表达式 myArray[0]
,想把代码里面的 myArray[0]
找出来,但是什么都没有匹配到!这是因为 []
作为元字符,并不是我们想要匹配的数组的符号。在这种情况下,我们必须进行转义了,转义的方法很简单,在它左边加上 反斜杠 \
,即 myArray\[0\]
,然后我们再来尝试一下。
import re
# 测试文本
text = """
var myArray = new Array();
if (myArray[0] == 0) {
}
"""
# 正则表达式
regexp = r'myArray\[0\]'
# 编译
pattern = re.compile(regexp)
# 匹配
m = pattern.search(text)
if m:
print(m.group(0))
else:
print("No match!")
转义之后,再进行匹配就可以取得预期的结果了。不过对于这种固定的文本,直接使用简单的文本匹配就可以做到了,没有必要使用正则表达式了。如果需要匹配的不仅仅是 myArray[0]
、myArray[1]
等,那么可以使用该正则表达式达到目标 myarray\[[0-9]\]
。但是这里有一个问题,它只能匹配下标是 0 - 9 的数组!所以想要写出一个适用范围的正则表达式还是很困难的! 如果你想要匹配 0-99 怎么写呢,学习了之后的内容,你就能写出来了!
提示 不仅仅是我们提到的这些,任何一个元字符都可以通过在前面加上一个反斜杠
\
进行转义。
警告 配对的元字符(比如
[
和]
)不用作元字符时必须被转义,否则正则表达式解析器可能会抛出一个错误。
对元字符进行转义需要用到 \
字符,所以它也是一个元字符,它的特殊含义是对其他元字符进行转义。因此,在需要匹配 \
本身的时候,我们必须把它转义为 \\
。
下面是一个例子,例子中的文本是一个包含反斜杠字符的文件路径(用于 Windows 系统)。假设我们想在一个 Linux 系统上使用这个路径,也就是说,需要把这个路径里的反斜杠字符(\
)全部替换为斜杠字符(/
)。
import re
# 测试文本
text = '\\home\\ben\\sales\\'
# 正则表达式
regexp = r'\\'
# 编译
pattern = re.compile(regexp)
# 匹配所有
rs = pattern.findall(text)
if rs:
print(rs)
for r in rs:
print(r, end=" ")
else:
print("No match!")
这里为了表示这个字符串,我也需要对它进行转义了。这次总共匹配到了4个反斜杠。直接输出,也是会被转义的,所以我又单个输出了。
4.2 匹配空白字符
元字符大致可以分为两种:一种是用来匹配文本的(比如 .
),另一种是正则表达式语法的组成部分([
和]
)。随着学习的深入,你将发现越来越多的这两种元字符,而我们现在要介绍的是一些用来匹配空白字符的元字符。
在进行正则表达式搜索的时候,我们经常会需要匹配文本中的非打印字符,下面通过表来列出它们。
元字符 | 说明 |
---|---|
[\b] | 回退(并删除)一个字符(backspace 键) |
\f | 换页符 |
\n | 换行符 |
\r | 回车符 |
\t | 制表符(tab键) |
\v | 垂直制表符 |
来看一个例子,下面的文本中包含一些以逗号分隔是数据记录(通常称为 CSV)。再进一步处理这些记录之前,你得先把夹杂在这些数据里的空白行去掉 。示例如下:
import re
import os
path = os.path.join(r'/xxxxRE/RE_python/chapter4/' "test.csv")
with open(path, "rb+") as f:
size = os.path.getsize(path)
# 测试文本
text = f.read(size)
# 正则表达式
regexp = '\r\n\r\n'
for t in text:
if t == "\r":
print("检测回车符")
# 编译
pattern = re.compile(regexp)
# 匹配所有
rs = pattern.findall(text.decode())
if rs:
print(rs)
else:
print("No match!")
这里的处理比较麻烦,我是将 test.csv 存入一个文件中了,然后使用二进制的读取方式,使用的时候再进行解码。因为直接读取的话,回车符 \r 被过滤掉了。
一般来说,需要匹配 \r
、\n
和 \t
(制表符)等空白符的情况比较多,而需要匹配其他空白字符的情况较少。
注意 使用正则表达式匹配行尾标记时,一定要注意文本文件所在的操作系统。
注意 只有对
f
和n
进行转义,它们才是元字符。否则,两者只是普通字符,只能匹配它们本身。
4.3 匹配特定的字符类型
字符集合(匹配一组字符中的某一个)是最常见的匹配形式,而一些常用的字符集合可以用特殊元字符来代替。这些元字符匹配的是某一个类字符。类元字符(class metacharacter)并不是必不可少的东西,但是它在实践中非常有用!
4.4.3.1 匹配数字(与非数字)
元字符 | 说明 |
---|---|
\d | 任何一个数字字符(等价于 [0-9 ]) |
\D | 任何一个非数字字符(等价于 [^0-9] ) |
下面来看几个例子:
import re
# 测试文本
text= """
var myArray = new Array();
...
if (myArray[0] == 0) {
...
}
"""
# 正则表达式
regexp = r'myArray\[\d\]'
# 编译
pattern = re.compile(regexp)
# 匹配
rs = pattern.findall(text)
if rs:
print(rs)
else:
print("No match!")
\[
匹配 [
, \d
匹配任意单个数字字符, \]
匹配 ]
,所以 myArray\[\d\]
等价于 myArray\[[0-9]\]
, 是它的一种简写形式, 而 myArray\[[0-9]\]
又是它 myArray\[[0123456789]\]
的简写形式.
提示 如你所见,正则表达式的写法几乎总是不止一种,挑选你自己觉得最舒服的那种写法即可.
警告 正则表达式的语法是区分字母大小写的.
\的
匹配数字,\D
与\d
的含义刚好相反,只匹配非数字. 即使是执行不区分字母大小写的匹配, 也是如此. 在这种情况下,匹配到的文本不区分字母大小写, 但特殊字符 (\d
) 会区分.
4.3.2 匹配字符数字(与非字母数字)
另一种频繁用到的字符集合是字母数字字符 (alphanumeric character), 其中包括: A 到 Z (大写和小写), 数字 0 到 9, 以及 _ (常用于文件名和目录名, 应用程序名, 数据库对象名等).
下面列出了用来匹配字母数字和非字母数字的字符类.
元字符 | 说明 |
---|---|
\w | 任何一个字母数字字符 (大小写均可) 或下划线字符 (等价于 [z-aA-Z0-9_] ) |
\W | 任何一个非字母数字或非下划线字符 (等价于 [^z-aA-Z0-9_] ) |
下面这个例子取自某个包含美国和加拿大城市邮政编码记录的数据库:
import re
# 测试文本
text = """
11213
A1C2E3
48075
48237
M1B4F2
90046
H1H2H2
"""
# 正则表达式
regexp = r'\w\d\w\d\w\d'
# 编译
pattern = re.compile(regexp)
# 匹配所有
rs = pattern.findall(text)
if rs:
print(rs)
else:
print("No match!")
在上面这个模式里, 交替出现的 \w
和 \d
元字符使得匹配结果里只包含加拿大城市的邮政编码.
注意 正则表达式很少有对错之分 (当然, 前提是它们能解决问题). 更多的时候, 正则表达式的复杂程度取决于模式匹配的严格程度.
4.3.3 匹配空白字符(与非空白字符)
最后要介绍的是空白字符类. 在前面的内容里, 你已经知道了用于匹配特定空白字符的元字符. 下面列出来用来匹配所有空白字符的元字符类.
元字符 | 说明 |
---|---|
\s | 任何一个空白字符 (等价于 [\f\n\r\t\v] ) |
\S | 任何一个非空白字符 (等价于 [^\f\n\r\t\v] ) |
注意 用来匹配退格字符的
[\b]
元字符不在\s
的覆盖范围内,\S
也没有将其排除.
4.3.4 匹配十六进制或八进制数值
尽管你可能不需要通过十六进制或八进制来引用某个字符, 但要指出的是, 这么做是可以的.
1. 使用十六进制值
在正则表达式里, 十六进制值 (基数为 16) 要用前缀 \x
来给出. 比如说, \x0a
(对应于 ASCII 字符 10, 也就是换行符) 等价于 \n
.
2. 使用八进制值
在正则表达式里, 八进制值 (基数是8) 要用前缀 \0
来给出, 数值本身可以是两位或三位数字. 比如说, \011 (对应于 ASCII 字符 9, 也就是制表符) 等价于 \t
.
注意 有不少正则表达式实现还允许使用
\从
前缀来只当各种控制字符. 比如说,\cZ
可以匹配 CTRZ-Z. 不过, 在实践中极少会用这种语法.
4.4 使用 POSIX 字符类
对元字符以及各种字符集合进行的讨论, 必须要提到 POSIX
字符类. POSIX
是一种特殊的字符类集, 也是许多 (但不是所有) 正则表达式实现都支持的一种简写形式.
下面列出12个 POSIX 字符类.
字符类 | 说明 |
---|---|
[:alnum:] | 任何一个字母或数字(等价于 [a-zA-Z0-9 ) |
[:alpha:] | 任何一个字母 (等价于 [a-zA-Z] ) |
[:blank:] | 空格或制表符 (等价于 [\t ] ) 注意\t 后面有一个空格! |
[:cntrl:] | ASCII 控制字符 (ASCII 0 到 31, 再加上 ASCII 127) |
[:digit:] | 任何一个数字 (等价于 [0-9] ) |
[:graph:] | 和 [:print:] 一样, 但不包括空格 |
[:lower:] | 任何一个小写字母 (等价于 [a-z] ) |
[:print:] | 任何一个可打印字符 |
[:punct:] | 既不属于 [:alnum:] , 也不属于 [:cntrl:] 的任何一个字符. |
[:space:] | 任何一个空白字符, 包括空格 (等价于 [\f\n\r\t\v ] ) 注意\v 后面有一个空格! |
[:upper:] | 任何一个大写字母 (等价于 [A-Z] ) |
[:xdigit:] | 任何一个十六进制数字 (等价于 [a-fA-F0-9] ) |
注意 JavaScript 不支持在正则表达式里使用
POSIX
字符类.
经过我测试及查阅资料, Python
的标准库 re
也不支持该字符类, 不过扩展的第三方库 regex
是支持的.
安装: pip3 install regex
import regex
# 测试文本
TEXT = """
body {
background-color: #fefbd8;
}
h1 {
background-color: #0000ff;
}
div {
background-color: #d0f4e6;
}
span {
backgroun-color: #f08970;
}
"""
# 正则表达式
REGEXP = r'#[[:xdigit:]][[:xdigit:]][[:xdigit:]][[:xdigit:]][[:xdigit:]][[:xdigit:]]'
# 编译
pattern = regex.compile(REGEXP)
# 匹配
rs = pattern.findall(TEXT)
if rs:
print(rs)
else:
print("No match!")
注意 这里使用的模式以
[[
开头, 以]]
结束 (两对方括号). 这是使用POSIX
字符类所必需的, 这单很重要.
说明:
这个东西了解就好了, 我们实际使用是不会使用这个的, 更多的还是通常的模式.