正则表达式
正则表达式(简称 Regex)是一种用于描述文本匹配规则的字符串模式。他通过特定语法组合普通字符和特殊字符(元字符)来定义字符串的搜索、验证或替换逻辑。被广泛应用于文本处理、编程和数据分析领域。
发展历史
正则表达式的理论在十九世纪五十年代开始逐渐完善。在六十年代正式被 Ken Thompson(肯大叔,Unix、B语言、UTF8、GO语言的发明者)引入编辑器 QED、随后又引入到 Unix 上的 ed 编辑器,以及后面大名鼎鼎的 grep 工具。自此正则表达式开始被广泛使用。之后又出现了两大流派他们也被正式确定为 POSIX 规范:
- BRE(Basic Regular Expression)即基本型正则表达式: grep、vi、sed 都属于 BRE
- ERE(Extended Regular Expression)即扩展型正则表达式: egrep、awk 都属于 ERE,他最重要的特性就是元字符不需要转移就可以使用
而在编程语言领域 1987 年出现的 Perl 引入了PCRE(Perl Compatible Regular Expression)即 Perl 兼容的正则表达式,他兼容 ERE 语法并且引入了非贪婪量词(*?
)、命名捕获组(?<name>...
)等特性。这些创新性设计也被其他大多数编程语言接受。
Tips
正则表达式的发展本质上遵循 理论数学 -> 工程实践 -> 标准化 -> 功能扩展 的演化路径。
PCRE 表达式全集
以下是 PCRE 表达式全集,BRE 和 ERE 是 PCRE 的子集。
Tips
这里的介绍主要依托于 Python 版本的 RCRE
基础语法
基础语法中大家都兼容,而且 BRE 也不需要进行转义。
语法结构 | 说明 | 示例 |
---|---|---|
普通字符 | 匹配任意普通字符(特殊字符需要转移) | 如 a 匹配字符"a" |
. |
匹配非换行符(\n )外的所有字符 |
如 gr*p 匹配 gr 后接一个任意字符,然后是 p |
^ |
锚定行的开始 | ^grep 匹配所有以grep开头的行 |
$ |
锚定行的结尾 | grep$ 匹配所有以grep结尾的行 |
[xyz] |
字符集合,匹配所包含的任意一个字符,注意这可以匹配特殊字符 | 如要匹配一个点可以 [.] |
[^xyz] |
排除型字符集合,匹配不包含在其中的任意一个字符 | [^.] 就是不包含点的字符串 |
[a-z]/[^a-z] |
比较特殊的写法,匹配字母 | - |
[a-z]/[^a-z] |
比较特殊的写法,匹配数字 | - |
量词
量词语法用于修饰前面的表达式来规定出现的次数的目的:
语法结构 | 说明 | 示例 | 兼容性说明 |
---|---|---|---|
* |
重复 0 次或多次 | .* 表示匹配任意不包含换行符的字符串,可以是空字符串 |
BRE 需要转义为 \* |
+ |
重复 1 次或多次 | .+ 表示匹配任意不包含换行符的字符串并且至少包含一个字符 |
BRE 需要转义为 \+ |
? |
重复 0 次或 1 次 | - | BRE 不支持 |
{m} |
重复 m 次 | 0{5} 匹配包含 5 个 0 的行 |
BRE 需要转义为 \{m\} |
{m, } |
至少 m 次 | 0{5,} 匹配至少包含 5 个 0 的行 |
BRE 需要转义为 \{m,\} |
{m, n} |
至少 m 次,不多于 n 次 | 0{5, 10} 匹配至少包含 5 个 0 的行且不多于 10 个 0 |
BRE 需要转义为 \{m, n\} |
非贪婪匹配
默认情况下正则表达式是贪婪匹配,例如 0{5, 10}
中他会尽可能多的匹配 0 的数量(主要意义在于分组中使用),在 PCRE 中引入了一种新的语法来实现非贪婪匹配:
语法结构 | 说明 | 示例 | 兼容性说明 |
---|---|---|---|
量词? |
表示前面的量词非贪婪匹配 | {m, n}? 表示匹配 m 个即可返回 |
PCRE 特有,grep 等需要特殊的参数来实现 |
In [4]: re.findall(r'(a{2,4}?)', 'aaaabcd')
Out[4]: ['aa', 'aa']
In [5]: re.findall(r'(a{2,4})', 'aaaabcd')
Out[5]: ['aaaa']
分组和引用
他是一个很强大的功能,他能够实现对前面已经匹配的内容的后续引用:
语法结构 | 说明 | 兼容性说明 |
---|---|---|
(pat) |
基础分组 | BRF需要转义 \(pat\) |
(?:pat) |
非捕获分组,仅用于组合表达式 | - |
(?P<name>pat) |
命名捕获组 | - |
\1 |
反向引用分组捕获的结果 | 兼容 |
(?P=name) |
方向引用命名分组捕获的结果 | - |
# 相当于匹配两个重复的单词
# 命名匹配同理,他只是更加的可读和编写方便
In [7]: re.findall(r'(.+) \1', "a ha ha")
Out[7]: ['ha']
断言
断言分为前向断言和后向断言,他们是一个很高级的用法。还有就是断言本身并不消耗字符,也就是匹配的结果中是不包含断言中匹配到的字符的:
语法结构 | 说明 |
---|---|
(?=...) |
正向肯定断言(后面有则匹配) |
(?!...) |
正向否定断言(后面没有则匹配) |
(?<=...) |
反向肯定断言(前面有则匹配) |
(?<!...) |
反向肯定断言(前面没有则匹配) |
# 后面有 10 或 11 才匹配
In [11]: re.findall(r'Windows(?=10|11)', 'Windows11')
Out[11]: ['Windows']
# 前面有 $ 才匹配
In [12]: re.search(r'(?<=\$)\d+', '$100')
Out[12]: ['100']
Tips
还有一种断言是 \b
来表示单词边界,他们被放置到转义序列中讲解
标识修饰符
用于修改正则表达式的一些行为,他们以 (?[aiLmsux])
的形式放置在正则表达式的开头,其中可以包含一个或多个具体含义如下:
修饰符 | 含义 |
---|---|
a | 仅限 ASCII 匹配 |
i | 忽略大小写 |
L | 特定语言区域匹配 |
m | 启用多行,控制 ^ $ 的行为 |
s | . 匹配所有字符 |
u | Unicode 匹配 |
x | 详细模式,允许使用(?#comment) 添加注释 |
其他
语法结构 | 说明 | 兼容性说明 |
---|---|---|
pat1\|pat2 |
分支,匹配 pat1 或者 pat2 | BRF不支持 |
(?#comment) |
添加注释 | - |
转义序列
转义序列主要有反斜杠(\
)定义,他有几种用法:
- 如果后面跟的不是数字或者字母,他用于消除该字符可能具有的特殊含义,核心用法就是将元字符作为普通字符看待,当然对于
\=
这样的形式也没什么意义他几乎等价于=
-
如果后面是 ASCII 数字和字母这具有特殊意义,还需要注意如果后面是非 ASCII 数字和字母特点就是大于 127 码点的字符则他被视为字面量,通过他也引入了很多特殊的用法其中包括
- 表示非打印字符,这个通常是 ASCII 中规定的大多数编程语言都支持的方式
转义序列 说明 \a
响铃(BEL) \b
退格符(BS) \f
换页符(FF) \n
换行符(LF) \r
回车符(CR) \t
水平制表符(TAB) \v
垂直制表符(VT) \000
八进制数 000 表示的字符 \xhh
十六进制数 hh 表示的字符 - 如果跟数字表示引用,他需要配合分组来完成
- 还有就是用于通用角色匹配,他通常能够被元字符替代,但是由于他非常常用因此使用转义序列来表示来简化表达式
转义序列 说明 \d
任意十进制数,ASCII 模式下等价于 [0-9]
\D
非十进制数的其他任意字符,ASCII 模式下等价于 [^0-9]
\s
匹配任意空白符,ASCII 模式下等价于 [ \t\n\r\f\v]
\S
匹配任意不属于空白符外的字符,ASCII 模式下等价于 [^ \t\n\r\f\v]
\w
匹配被视为字母数组的字符,ASCII 下等价于 [a-zA-Z0-9_]
对应合法的标识符序列\W
\w
取反- 最后就是一些简单的断言,他们通常也能够被元字符替代,但是有他们能够极大的简化表达式
转义序列 说明 \b
单词边界匹配,他视 \w
为单词,即所有的\W
都属于单词边界,例如\bat\b
将匹配到'at' 'at.' '(at)' 'as at'
等但不能匹配到'attempt'
\B
匹配非单词边界, at\B
就可以匹配'attempt'
但是没有办法匹配到'at.'
这类的\A
匹配字符串开始,与 ^
不同的是在多行模式下依然匹配字符串开始,^
则匹配行开始\Z
匹配字符串结尾,与 %
不同的是在多行模式下依然匹配字符串结尾,$
则匹配行结尾
shell 中转义处理
在 shell 中他会默认给你进行转义,例如 grep \.bash
实际上发送给 grep 的是转义后的结果 .bash
这就违背了我们的意愿,而要想在 shell 中不转义可以使用单引号包围:
Tips
因此在 SHELL 中推荐所有的正则表达式都用单引号包围,有点类似 Python 中的 r""
字符串模式
元字符
整个正则表达式中存在 12 个元字符,他们被解释为不同的含义:
\
: 通常用于转义字符,也可以跟普通字符来扩展语义^
: 断言字符串的开始$
: 断言字符串的结束.
: 匹配任意除了\n
外的所有字符[ ]
: 用于定义字符集合|
: 用于定义替代分支( )
: 用于定义分组*
: 量词表示 0 或更多+
: 量词表示 1 或更多?
: 量词表示 0 或 1,如果修饰量词表示非贪婪匹配{ }
: 量词表示指定大小
而特殊的方括号用于定义字符集合,大多数元字符在其中都被解释为普通字符,当然也有一些例外:
\
: 通用转义字符^
: 必须位于第一个字符表示取反-
: 指定字符范围,标准的就是a-z
和0-9
当然1-6
也可以,他避免了用户写[0123456789]
这样复杂的语法[ ]
: 匹配他们自身需要转义\[ \]