正则表达式
概述
- 正则表达式,Regular Expression,缩写为regex、regexp、RE等
- 正则表达式是文本处理极为重要的技术,用它可以对字符串按照某种规则进行检索、替换。
参考https://www.w3cschool.cn/regex_rmjc/
分类
- BRE
- 基本正则表达式,grep、sed、vi等软件支持。vim有扩展
- ERE
- 扩展正则表达式,egrep(grep-E)、sed -r等
- PCRE
- 几乎所有高级语言都是PCRE的方言或者变种。Python从1.6开始使用SRE正则表达式引擎,可以认为是PCRE的子集,对应模块re。
##基本语法
- 几乎所有高级语言都是PCRE的方言或者变种。Python从1.6开始使用SRE正则表达式引擎,可以认为是PCRE的子集,对应模块re。
元字符metacharacter
代码 | 说明 | 举例 |
---|---|---|
. | 匹配除换行符外的任意一个字符 | . |
[abc] | 字符集合,只能表示一个字符位置。匹配包含的任意一个字符 | [abc]匹配plain中的’a’ |
[^abc] | 字符集合,只能表示一个字符位置。能匹配除abc外的所有字符。 | [^abc]可以匹配plain中的‘p’,‘l’,'i’或者‘n’ |
[a-z] | 字符返回,也是个集合,表示一个字符位置匹配所有包含的任意一个字符 | 常用[A-Z][0-9],或者[a-t] |
[^a-z] | 匹配除a到z外的所有字符 | |
\b | 单词的边界 | \bb在文本中吵到单词中b开头的b字符 |
\B | 不匹配单词的边界 | t\B包含t的单词但是不以t结尾的t字符,例如write。\Bb不以b开头的含有b的单词,例如able |
\d | 等价于[0-9] | |
\D | 等价于[^0-9] | |
\s | 匹配1位空白字符,包括换行符、制表符、空格[\f\r\n\t\v ] | |
\S | 匹配1位非空白字符[^\f\r\n\t\v ] | |
\w | 匹配[a-zA-Z0-9_],包括中文的字等。 | |
\W | 匹配\w之外的字符 |
字符转义
- 凡是在正则表达式中有特殊意义的符号。例如.^*\等符号,匹配时需要表示该符号可以使用转义符号转义。
- 转义符号右斜杠\
- \r,\n转义后表示回车和换行
重复
代码/语法 | 说明 |
---|---|
* | 重复零次或多次 |
+ | 至少重复一次或多次 |
? | 只能重复零次或一次 |
{n} | 重复n次 |
{n,} | 最少重复n次或更多次 |
{n,m} | 最少重复n次,最多重复m次 |
分支条件
代码 | 说明 | 举例 |
---|---|---|
x|y | 匹配x或者y | wood took foot food 使用w|food或者(w|f)ood |
分组捕获
- 正则表达式默认匹配项返回值会放在Match组中,组的编号通常默认为0,如果在正则表达式中使用了分组和命名分组,那么会同时生成对应的组,如果是命名分组,生成的组名会和命名的组名相同,如果是使用的不是命名分组,那么生成的组的序号默认从1开,依次对应添加对应的组。不同的组会返回不同组的匹配结果。
名称 | 代码 | 说明 | 举例 |
---|---|---|---|
捕获 | |||
分组捕获 | (pattern) | 1.使用小括号指定一个子表达式,也叫分组 2.捕获后会制动分配组号,从1开始 3.可以改变优先级 | |
重复利用分组 | \数字 | 匹配对应的分组 | (very) \1匹配very very,但捕获的组group是very |
不需要分组 | (?:exp) | 如果仅仅为了改变优先级,就不需要捕获分组 | |
命名分组 | (?< name>exp)(?'name'exp) | 命名分组捕获,但是可以通过name访问分组Python语法必须是(?P< name>exp) | |
零宽断言 | wood took foot food | ||
零宽度正预测先行断言(也有人称为:前端锚定) | (?=exp) | 例如:【exp2(?=exp)】匹配exp2。断言自身(exp2)出现的位置的后面(即右边)能匹配表达式exp。 也就是说"匹配字符"后面跟个先行断言。判断匹配的字符后面是否满足断言条件。 | 【f(?=oo)】会匹配f。但匹配到的f后面在原字符中一定会有oo |
零宽度正回顾后发断言(也有人称为:后端锚定) | (?<=exp) | 例如:【(?<=exp)exp2】匹配exp2。断言自身(exp2)出现的位置的前面(即左边)能匹配表达式exp。 也就是说"匹配字符"前面加个后发断言。判断匹配的字符前面是否满足断言条件。 | 【(?<=f)ood】会分别匹配ood、ood但其在原文中对应位置前面一定有f存在。 【(?<=t)ook】会匹配ook,但ook在原文中对应位置前面一定存在t |
负向零宽断言 | |||
零宽度负预测先行断言 | (?!exp) | 例如:【exp2(?!exp)】匹配exp2。断言次位置(exp2)后面不能匹配表达式exp。 与零宽度正预测先行断言相反。 | 【\d{3}(?!\d)】匹配3位数字,断言3位数字后面一定不能是数字 【foo(?!d)】匹配foo。但foo在原文中的对应位置后面一定不会出现d |
零宽度负回顾后发断言 | (?< !exp>) | 例如:【(?< !exp)exp2】匹配exp2。断言此位置(exp2)的前面不能匹配表达式exp。 与零宽度正回顾后发断言相反 | 【(?< !f)ood】匹配ood,但ood在原文对应位置的前面一定不是f。 |
注释 | |||
注释 | (?#comment) | 在正则表达式中插入注释(不推荐使用) | 【f(?=oo)(?#这个后断言不捕获)】 |
- 注意
- 断言不占分组号。断言如同条件,只是要求匹配必须满足断言的条件。
- 使用正则表达式时,能用简单表达式,就不要复杂的表达式
- 注释也可以如下方式写
(?<= # 断言要匹配的文本的前缀
<(\w+)> # 查找尖括号括起来的字母或数字(即HTML/XML标签)
) # 前缀结束
.* # 匹配任意文本
(?= # 断言要匹配的文本的后缀
<\/\1> # 查找尖括号括起来的内容:前面是一个"/",后面是先前捕获的标签
) # 后缀结束
贪婪与非贪婪
- 正则表达式默认是:贪婪模式,也就是说尽量多匹配更长的字符串。
- 非贪婪:在重复符号后面加上一个?问号,就是非贪婪。即:尽量的少匹配。
代码 | 说明 |
---|---|
*? | 重复匹配任意次,但尽可能少重复 |
+? | 重复匹配1次或更多次,但尽可能少重复 |
?? | 重复匹配0次或1次,但尽可能少重复 |
{n,m}? | 重复n到m次,但尽量可能少重复 |
{n,}? | 重复n次以上,但尽量可能少重复 |
引擎选项(处理选项)
代码 | 名称 | 说明 | Python |
---|---|---|---|
ignoreCase | 忽略大小写 | 匹配时不区分大小写 | re.I re.IGNORECASE |
Singleline | 单行模式 | 单行模式。 注意:单行模式中.可以匹配所有字符,包括\n | re.S re.DOTALL |
Multiline | 多行模式 | 多行模式。 注意:更改^和 的 含 义 , 使 他 们 分 别 在 任 意 一 行 的 行 首 和 行 尾 匹 配 , 而 不 仅 仅 在 整 个 字 符 串 的 开 头 和 结 尾 匹 配 。 ( 在 此 模 式 下 , 的含义,使他们分别在任意一行的行首和行尾匹配,而不仅仅在整个字符串的开头和结尾匹配。(在此模式下, 的含义,使他们分别在任意一行的行首和行尾匹配,而不仅仅在整个字符串的开头和结尾匹配。(在此模式下,的精确含义是:匹配\n之前的位置以及字符串结束前的位置。) | re.M re.MULTILINE |
IgnorePatternWhitespace | 忽略空白 | 忽略表达式中的非转义空白,并启用由#标记的注释。 1.如果要使用空白字符用转义。 2.#可以用来做注释 | re.X re.VERBOSE |
ExplicitCapture | 显示捕获 | 仅捕获已被显示命名的组 |
- 注意:
- 多行模式和单行模式这两个选项之间没有任何关系,除了他们的名字比较相似,让人容易感到疑惑以外。不会不会互相干扰。
- 单行模式,多行模式,默认模式中的区别
- 默认模式:
【.】 匹配任意字符,但不包括换行符\n
【^】 匹配首部(字符串的开始位置)。即行首。不会受换行符\n的影响而重新将\n的后面认为是新的首部。(即待匹配的文本是一行)
【&】 匹配尾部(字符串的结束位置)。即行尾。不会受换行符\n的影响而重新将\n的前面认为是新的尾部。(即待匹配的文本是一行) - 单行模式:
【.】被重新定义 可以匹配所有字符,包括换行符\n,
【^】表示整个字符串的开头,与默认模式相同
【$】表示整个字符串的结尾。与默认模式相同 - 多行模式:
【.】匹配任意字符,但不包括换行符\n
【^】被重新定义 可以匹配首部(字符串的开始位置),和换行符\n后面一行的行首位置。
【&】被重新定义 可以匹配尾部(字符串的结束位置),和换行符\n前面一行的行尾位置。
总结
- 单行模式,只改变了字符【.】的意义。多行模式,只改变字符【^和$】的意义。互不干扰。
- 正则表达式中每一种模式都是独立修改的,都不会互相干扰,模式只是表示一种状态。不同模式可以混合使用。
- 正则表达式中只认换行符\n,而windows中的换行符是\r\n,字符串中有\r\n这种\r的隐藏字符注意匹配结果是否会多出\r这个空白字符。
示例
- 匹配一个0-999之间的任意数字
正则表达式为:
"([\d]{1,3})\s"
- 匹配邮箱地址
正则表达式为:
"[\w-.]*@[\w-.]*\.[a-zA-Z]*(?:\.[a-zA-Z]*)?"
- 匹配座机号和手机号
正则表达式为:
"(?<=\D?[^1-9])(?:(?:\d{3}(?:(?:\d-)|(?:-\d))\d{7})|\d{11})(?=\D)"
匹配字符串为:
ab15936278529
025-83105736
0543-5467328
015893526408
121536252536
152425648
运行结果:
- 匹配强密码
- 要求密码必须由10-15位指定字符组成:
2. 十进制数字
3. 大写字母
4. 小写字母
5. 下划线 - 要求四种类型的字符都要出现的合法的强密码
- 要求密码必须由10-15位指定字符组成:
- 使用先行断言匹配
正则表达式为:
"(^(?=.*[a-z])(?=.*[0-9])(?=.*[A-Z])(?=.*[_*@#$%]).{10,15}$)"
- 使用后发断言匹配
正则表达式为:"(^.{10,15}$(?<=[a-z].*)(?<=[1-9].*))(?<=[A-Z].*)(?<=[_*()&^].*)"
或者:
正则表达式为: “^.{10,15}(?<=[a-z].*)(?<=[1-9].*)(?<=[A-Z].*)(?<=[_*()&^].*)$
”
正则表达式高级部分
平衡分组与递归匹配
- 知识储备:
- 零宽负向先行断言,无exp表达式。(?!)该表达式正确,但不能匹配任何值。即永恒无法匹配。即断言任何位置都永恒不成立。等价于if False。同理(?<!)也是永恒不成立。
- (?!) #断言永恒不成立,等价于if False
- (?=) #断言永恒成立,等价于 if True
- (?<!) #断言永恒不成立,等价于if False
- (?<=) #断言永恒成立,等价于if True
- 零宽负向先行断言,无exp表达式。(?!)该表达式正确,但不能匹配任何值。即永恒无法匹配。即断言任何位置都永恒不成立。等价于if False。同理(?<!)也是永恒不成立。
- 假设匹配字符:【<200*<23+25>+300>】这样的字符,如果使用"<.+>“会比匹配到最左端“<”和最有端“>”之间的内容。如果表达式变成【<200*<23+25>+300>>】这种匹配到的结果中“<”符号就比“>”符号多。如果需求是匹配的结果中要使得”<"与“>”成对存在。这个时候就需要用到平衡分组。
代码 | 说明 |
---|---|
(?'group’exp) | 把捕获的内容exp命名为group,并压如堆栈(Stack) |
(?’-group’exp) | 从堆栈上弹出最后压入堆栈名为group的捕获内容exp,如果堆栈本来为空,则分组的匹配失败 |
(?(group)yes|no) | 如果堆栈上存在以group的捕获内容,继续匹配yes部分的表达式,否则继续匹配no部分的表达式。 |
(?!) | 零宽负向先行断言,由于没有后缀表达式,试图匹配总是失败,即永恒False,相当于if False |
我们需要做的是每碰到了"<",就在压入一个"Open",每碰到一个">",就弹出一个"Open",到了最后就看看堆栈是否为空--如果不为空那就证明左括号比右括号多,那匹配就应该失败。正则表达式引擎会进行回溯(放弃最前面或最后面的一些字符),尽量使整个表达式得到匹配。
对于尚需问题相应的正则表达式代码如下:
< #最外层的左括号
[^<>]* #最外层的左括号后面的不是括号的内容
(
(
(?'Open'<) #碰到了左括号,在黑板上写一个"Open"
[^<>]* #匹配左括号后面的不是括号的内容
)+
(
(?'-Open'>) #碰到了右括号,擦掉一个"Open"
[^<>]* #匹配右括号后面不是括号的内容
)+
)*
(?(Open)(?!)) #在遇到最外层的右括号前面,判断黑板上还有没有没擦掉的"Open";如果还有,则匹配失败
> #最外层的右括号
简写为:"<[^<>]*((?'xdd'<)[^<>]*)+((?'-xdd'>)[^<>]*)+(?(xdd)(?!)|(?=))>
"
- 平衡分组最常见的应用就是匹配HTML,下面这个例子可以匹配嵌套的<div>标签:
<div[^>]*>[^<>]*(((?'Open'<div[^>]*>)[^<>]*)+((?'-Open'</div\s*>)[^<>]*)+)*(?(Open)(?!))</div\s*>
非回溯表达式
语法:(?>exp)
exp在下面称为子表达式,为任何正则表达式模式。
非回溯表达式,也称为 贪婪的子表达式。即子表达式能最多匹配到的结果,绝不会为了适应其他表达式而缩小自己匹配的范围。
本人自己理解为:一把定江山。
官方解释:
通常,如果正则表达式包含一个可选或可替代匹配模式并且备选不成功的话,正则表达式引擎可以在多个方向上分支以将输入的字符串与某种模式进行匹配。 如果未找到使用第一个分支的匹配项,则正则表达式引擎可以备份或回溯到使用第一个匹配项的点并尝试使用第二个分支的匹配项。 此过程可继续进行,直到尝试所有分支。
仅当嵌套构造不均衡时,才应该定义 (?>子表达式) 语言构造禁用回溯。 正则表达式引擎将在输入字符串中匹配尽可能多的字符。 在没有任何进一步匹配可用时,它将不回溯以尝试备用模式匹配。 (也就是说,该子表达式仅与可由该子表达式单独匹配的字符串匹配;子表达式不会尝试与基于该子表达式的字符串和任何该子表达式之后的子表达式匹配。)
不知道是我理解问题?还是做翻译的没翻译好。反正刚接触,第一遍看,没看懂。等懂了点后再看,发现好高端,有道理。
- 举例简单理解什么是非回溯表达式
需要匹配字符如下:
aaaab
ccca
dddd
eeee
- 使用表达式
(?>(\w)\1+).\b
能匹配到"aaaab"和"ccca" - 使用表达式
(\w)\1+.\b
能全部匹配即:“aaaab”,“ccca”,“dddd”,“eeee”
下面介绍中定义表达式:
(?>(\w)\1+).\b
为exp1。
(\w)\1+.\b
为exp2。 - 在exp1匹配aaab时,由于使用了非回溯表达式(?>(\w)\1+),在匹配相同的a时,使用了贪婪子表达式。也就是匹配所有a能重复出现字符作为非回溯子表达式中的匹配项,再继续想后匹配“.”绝不为了适应".“的匹配而缩小自己能匹配的范围。所以,在匹配dddd和eeee时。会直接使用(?>(\w)\1+)非回溯表达式匹配完成,再继续向后查找一个任意字符”."发现没有。就判断不符合标准。
- 在exp2匹配时。对于“dddd”匹配时,"(\w)\1+“先会将整个dddd全部匹配。在寻找.\b时发现无法匹配,就对”(\w)\1+"匹配的结果进行回溯一个值,而满足对.\b的匹配。所有exp2能够匹配全部。
3.当然,如果想只匹配“aaaab”和“ccca”也可以使用断言来达到相同的效果,如:(\w)\1+.(?<!\1)\b
- 总结:
- 非回溯仅仅匹配由非回溯子表达式匹配的字符串; 不会尝试匹配与基于该子表达式其后跟随的任何该子表达式。
- 由于非回溯表达式使用了足够贪婪模式,所以使用时,在有些情况下能提高正则表达式的匹配效率。因为防止了子表达式中的回溯问题。