Cane's Blog

Cane

【Re】正则表达式

18
2020-12-25

常用字符及其含义

字符

描述

.

匹配除“\n”和"\r"之外的任何单个字符

\d

匹配任何数字字符,和 [0-9] 相同

\D

匹配任何非数字字符,和 [^0-9] 相同

\w

匹配包含 a-zA-Z0-9 和下划线的字符串

\W

匹配任何没有下划线和子母数字的字符串

\s

匹配任何空白字符

\S

匹配任何非空白字符

\b

匹配是否到达了单词边界

\B

匹配是否没有到达单词编辑

\

转义符

^

匹配输入字行首

$

匹配输入行尾

x|y

匹配x或y

[abc]

字符集合,匹配任何包含小写字母 a、b、c 的字符串

[^abc]

负值字符集合,匹配任何不包含小写字母 a、b、c 的字符串

[a-z]

字符范围,匹配 a 到 z 范围内的任意小写字母字符

*

匹配前面的子表达式任意次

+

匹配前面的子表达式一次或多次

?

匹配前面的子表达式零次或一次

{n}

匹配前面的子表达式确定的 n 次

{n,}

匹配前面的子表达式至少匹配n

{n,m}

匹配前面的子表达式最少 n 次且最多 m 次

( )

将表达式定义为「组」(group),并且将匹配这个表达式的字符保存到一个临时区域(一个正则表达式中最多可以保存9个),它们可以用 「\1」 到「\9」 的符号来引用

贪婪、非贪婪与侵占

贪婪:第一个匹配对象「往后尽可能多的」匹配,可以回退

非贪婪:第一个匹配对象「不往后尽可能多的」匹配

侵占:第一个匹配对象「往后尽可能多的」匹配,但不可以回退

匹配模式:.*?(b.*b).*
匹配结果:booooooobbbb
匹配释义:左端的 b 非贪婪匹配,匹配到第一个符和的 b 便不再往后匹配,右端的 b 贪婪匹配,匹配至不能再匹配为止

匹配模式:.*(b.*?b).*
匹配结果:bb
匹配释义:左端的 b 贪婪匹配,匹配到不能再往后匹配为止,右端的 b 非贪婪匹配,匹配至第一个结束

匹配模式:.*?(b.*?b).*
匹配结果:booooooob
匹配释义:两端b非贪婪匹配,即匹配到第一个结束

贪婪非贪婪与最短匹配无关

案例:00000aba110aa000

贪婪:a(.*)a
匹配结果:aba110aa

非贪婪:a(.*?)a
匹配结果:aba

贪婪非贪婪,与是否在整个文本中想要匹配的对象是否最短无关
上述案例,最短结果应该是「aa」,仅用贪婪非贪婪是无法实现匹配结果最短的结果的。

贪婪与侵占的对比

  • 贪婪模式

    表达式中单独使用正则量词 ? 、* 、+ 、{n,m} 的情况。

    特点:一次尽量多的匹配字符,但可以回退。

  • 侵占模式

    贪婪模式字符的后面添加一个 + 来表示的。

    特点:一次尽量多的匹配字符,但不可以回退。

目标字符串:m232hjdhfd7474

贪婪正则: \w+[a-z]

将上面的正则拆分成 \w+[a-z] 两个子表达式来看,\w+ 称作 p1,[a-z] 称作 p2。
p1 属于贪婪模式,会一次性吃掉它所能吃掉的所有的字符,也就是子串 232hjdhfd7474;这样的话,就只剩下 $ 与 p2 进行匹配了,显然是匹配失败的。所以,第二轮尝试匹配时,p1 会吐出一个字符4,但此时 p2 还是得不到匹配。反复的这样吐出回退,直到吐出字符 d 时,d 能与 p2 完成匹配,这时正则表达式会返回一次成功匹配的结果,即字符串 232hjdhfd

侵占正则: \w++[a-z]

将上面的正则拆分成 \w++[a-z] 两个子表达式来看,\w++ 称作 p1,[a-z] 称作 p2。
p1 属于侵占模式,它会一次性吃掉它所能够吃掉的所有字符,即子串 232hjdhfd7474,而且 p1 不会回退字符;这时,只剩下 $ 与 p2 进行匹配了,显然是匹配失败的,所以整个表达式匹配失败。

捕获与非捕获

  • 捕获

    (exp) : 匹配 exp ,并捕获文本到自动命名的组里(「\1」)

    (?<name>exp) :匹配 exp ,并捕获文本到名称为 name 的组里,也可以写成 (?'name'exp)

  • 非捕获

    (?:exp1)exp2:匹配到的 exp1 不放入到捕获组里面

import re

a = 'abcssdad123'
pattern1 = re.compile(r'(abc)(\w{4})')
pattern2 = re.compile(r'(?:abc)(\w{4})')

res1 = pattern1.search(a)
res2 = pattern2.search(a)
print(res1.groups())
print(res2.groups())

>> ('abc', 'ssda')
>> ('ssda',)

零宽断言

零宽断言都是属于非捕获的

表达式

描述

exp1(?=X)

正向肯定预查,仅当子表达式 X 在表达式 exp1 「右侧匹配」时才继续匹配

exp1(?!=X)

正向否定预查,仅当子表达式 X 在表达式 exp1 「右侧不匹配」时才继续匹配

(?<=X)exp1

反向肯定预查,仅当子表达式 X 在表达式 exp1「左侧匹配」时才继续匹配

(?<!X)exp1

反向否定预查,仅当子表达式 X 在表达式 exp1「左侧不匹配」时才继续匹配

[a-z](?=3)
匹配字母右侧「跟着」3 的字母

[a-z](?!3)
匹配字母右侧「不跟着」3 的字母

(?<=a)\d
匹配数字左侧的字符 「是」 a 的数字

(?<!=\a)\d+
匹配数字左侧的字符 「不是」 a 的数字

(?<!4)56(?=9)
查找56,要求左侧不能是4,右侧必须是9。5569的「56」匹配 ,4569中的「56」不匹配。
import re

a = 'a3 b4'

print(re.compile(r'[a-z](?=3)').findall(a))
print(re.compile(r'[a-z](?!3)').findall(a))
print(re.compile(r'(?<=a)\d').findall(a))
print(re.compile(r'(?<!a)\d').findall(a))

>> ['a']
>> ['b']
>> ['3']
>> ['4']

模式修正符

模式修正符

描述

i

匹配时不区分大小写

g

全局匹配,查找所有的匹配项

m

多行匹配,使边界字符「^」和「$」匹配每一行的开头和结尾

s

特殊字符圆点,使「.」中包含换行符 「\n」,会将整段字符串视为单行,也称单行模式

x

将表达式中的空白忽略

e

弃用(配合函数preg_replace()使用, 可以把匹配来的字符串当作正则表达式执行)

A

强制从字符串开头匹配,相当于给表达式加了一个「^」

D

强制以表达式结尾,相当于给表达式加了一个「$」

U

正则表达式的特点是比较「贪婪」,使用该模式可以取消贪婪模式

-

禁用

多个模式修正符可以一起用

(?i-s)
作用:不区分大小写,并禁用单行模式

模式修正符可以限定范围

表达式:(?i)ab (也可以写成 (?i:)ab
作用:表示对 (?i) 后的所有字符都开启不区分大小写的开关,故它可以匹配ab、aB、Ab、AB

表达式:(?i:a)b
表示只对a开启不区分大小写的开关,故它可以匹配ab和Ab,不能匹配aB和AB。

Python中的正则

  • match: re.match(pattern, string[, flags])

    从首字母开始开始匹配,string 如果包含 pattern 子串,则匹配成功,返回 Match 对象,失败则返回 None。若要 string 完全匹配 pattern,pattern 要以 $ 结尾。

  • search: re.search(pattern, string[, flags])

    若 string 中包含 pattern 子串,则返回 Match 对象,否则返回 None ,注意,如果 string 中存在多个 pattern 子串,只返回第一个。

  • findall: re.findall(pattern, string[, flags])

    返回string中所有与pattern相匹配的全部字串,返回形式为数组。

  • finditer: re.finditer(pattern, string[, flags])

    返回string中所有与pattern相匹配的全部字串,返回形式为迭代器。

  • match 对象

    group()返回类型为字符串,表示返回整个匹配对象作为一个字符串

    group(1)返回类型为字符串,表示返回正则表达式的第1个分组匹配的对象,group(0)等同于group()返回整个匹配对象

    groups()返回类型为元祖,表示返回正则表达式每个分组匹配的对象依次放进元祖里,如果正则表达式没有(),则返回空元祖()。

参考文献

  1. 零宽断言和非捕获组1

  2. 零宽断言和非捕获组2

  3. 正则表达式简易手册

  4. 正则表达式文档