【备用】正则表达式
正则表达式(regular expression, 简称RE)描述了一种字符串匹配的模式(pattern),可以用来检查一个字符串是否含有某种子串、将匹配的子串替换或者从某个串中取出符合某个条件的子串等。
通过使用正则表达式,可以:
测试字符串内的模式。 例如,可以测试输入字符串,以查看字符串内是否出现电话号码模式或信用卡号码模式。这称为数据验证。
替换文本。 可以使用正则表达式来识别文档中的特定文本,完全删除该文本或者用其他文本替换它。
基于模式匹配从字符串中提取子字符串。 可以查找文档内或输入域内特定的文本。例如Linux中的
grep
命令使用正则表达式查找文件里符合表达式条件的内容。
基本的正则表达式
正则表达式语法由字符和操作符构成, 字符例如字母和数字表示他们自身。很多标点符号只有被转义时才匹配自身,否则它们表示特殊的含义。
最简单的正则表达式是一串字符,例如woodchuck
表达式可以匹配字符串"woodchuck",!
可以匹配感叹号"!"。正则表达式是大小写敏感的,所以woodchuck
无法匹配到"Woodchuck",可以使用[wW]oodchuck
匹配"Woodchuck"和"woodchuck"。其中[...]
用来表示单个字符的取值范围。
下面结合例子来熟悉一下正则表达式,用下划线来表示正则匹配到的字符串。
[wW]oodchuck
Woodchuck或woodchuck
Woodchuck
大小写敏感,[]
表示字符取值范围
[abc]
'a', 'b'或'c'
In uomini, in soldati
[]
表示字符取值范围
[0-5]
0-5之间的数字
plenty of 7 of 5
-
简单指定范围
[A-Z]
一个大写字母
Mr.Li
-
简单指定范围
[0-9]
单个数字,等价于[1234567890]
Chapter 1
-
简单指定范围
[^A-Z]
非大写字母
Mr
[^...]
表示非指定字符之外的字符。
[e^]
匹配'e'或者'^'
look up ^
^
不在[]
中第一个字符时不表示“非”
a^b
匹配"a^b"
look a^b
同上
^the
匹配字符串的the
the dog or the cat
^
在模式第一个字符时表示字符串的开头
the dog\.$
匹配以the dog.结尾的字符串
the cat or the dog.
$
表示字符串的结尾
woodchucks?
匹配woodchuck或woodchucks
woodchuck
?
表示前一个字符出现0次或1次
colou?r
color或colour
colour
同上
aa*
a或aa或aaa...
aaaa
*
表示前一个字符出现0次或多次
[ab]*
0个或多个连续的a或b
aaa, abab, bbb
同上
[0-9][0-9]*
或[0-9]+
1个或多个连续的数字
$123
+
表示前一个字符出现1次或多次
beg.n
beg+任意字符+n
begin, beg'n, begun
.
表示任意单个字符(除了换行符)
P14
匹配单个字符
1.匹配纯文本,比如Ben,think等。
可能有多个匹配结果,比如Python中的re.find是只返回第一个匹配结果,re.findall是返回所有匹配结果。
字母是大小写敏感的,B只能匹配B,不能匹配b。Python库中的参数re.I可以设置是否忽略大小写。
2.匹配任意单个字符用.
(实际上它不能匹配换行符)
3.匹配特殊字符。一些字符在正则表达式里有特殊的含义,比如.
表示任意单个字符,如果想匹配.
这个字符,就要用转移字符\
,即\.
就可以匹配.
这个字符。
匹配一组字符
1.匹配多个字符中的某一个用[]
,比如[abc]
表示匹配a或b或c
2.利用字符集合区间,比如[0-9]
表示匹配0-9里的任意数字, 其它的字符集合区间还有[A-Z]
, [a-z]
, [a-f]
等
3.取非匹配^
,也就是说,除了那个字符集合里的字符,其它字符都可以匹配。比如[^0-9]
匹配任意不是数字的字符。
re.search是Python的正则表达式操作函数,表示“进行正则表达式匹配”,charStr是需要判断的字符串,"[0123456789]"则是以字符串形式给出的正则表达式。
在很多语言中还可以用转义序列\xhex
来表示一个字符,其中\x
是固定前缀,表示转义序列的开头,num
是字符对应的码值(Code Point),是一个两位的十六进制数值。比如字符A的码值是41(十进制则为65),所以也可以用\x41
表示。
使用元字符
1.元字符是一些在正则表达式里有有特殊含义的字符,这些字符无法用来代表它本身。只有在元字符前面加上反斜杠\
才能用来表示它本身。
比如要匹配\
本身,也要在前面加上\
,得到\\
。
2.匹配空白字符
[\b]
回退(并删除)一个字符(Backspace键)
\f
换页符
换行符
回车符
制表符(Tab键)
\v
垂直制表符
比如\r
匹配一个“回车+换行”组合,有许多操作系统(比如Windows)都把这个组合用作文本行的结束标签。Unix和Linux系统只使用一个换行符来结束一个文本行。
3.匹配特定的字符类别
\d
任何一个数字字符(等价于[0-9]
)
\D
任何一个非数字字符(等价于[^0-9]
)
\w
任何一个字符数字字符(大小写均可)或下划线字符(等价于[a-zA-Z0-9_]
)
\W
任何一个非字母数字或下划线字符(等价于[^a-zA-Z0-9_]
)
\s
任何一个空白字符(等价于[\f\n\r\t\v]
)
\S
任何一个非空白字符(等价于[^\f\n\r\t\v]
)
十六进制,比如\x0A
对应于ASCII字符10(换行符),其效果等价于
八进制,比如\011
对应于ASCII字符9(制表符),其效果等价于。
\s\S
, \w\W
, \d\D
可以匹配任意字符,大小写是互补的。
4.使用POSIX字符类
POSIX字符类是许多(但不是所有)正则表达式实现都支持的一种简写形式。
[:alnum:]
任何一个字母或数字(等价于[a-zA-Z0-9]
)
[:alpha:]
任何一个字母(等价于[a-zA-Z]
)
[:blank:]
空格或制表符(等价于[\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 ]
)
[:upper:]
任何一个大写字母(等价于[A-Z]
)
[:xdigit:]
任何一个十六进制数字(等价于[a-fA-F0-9]
)
注意[]
是POSIX字符类本身的组成部分
重复匹配
匹配一个或多个字符:给字符(或字符集合)加上+
字符作为后缀
匹配零个或多个字符:给字符(或字符集合)加上*
字符作为后缀
匹配零个或一个字符:给字符(或字符集合)加上?
字符作为后缀
为重复匹配次数设定一个精确的值,{数字}
为重复匹配次数设定一个区间,{最小值,最大值}
,注意是闭区间
匹配“至少重复多少次”,{最小值,}
防止过度匹配:*
和+
都是“贪婪型”元字符,它们在进行匹配时会尽可能地从一段文本的开头一直匹配到这段文本的末尾,而不是从这段文本的开头匹配到碰到第一个匹配为止。
这些元字符的“懒惰型”版本是给贪婪型元字符加上一个?
后缀。
常用的贪婪型元字符有*
, +
, {n,}
,他们的懒惰型版本分别是*?
, +?
, {n,}?
位置匹配
\b
用来匹配一个单词的开始或结尾(单词边界),它匹配的是一个这样的位置,这个位置位于一个能够用来构成单词的字符(字母、数字和下划线,也就是与\w
相匹配的字符)和一个不能用来构成单词的字符(也就是与\W
相匹配的字符)之间。它可以匹配句子的第一个单词。
\B
匹配非单词边界,即匹配字母数字下划线之间,或者非字母数字下划线之间(取决于想匹配的字符)。
比如\B-\B
去匹配Please enter the nine-digit id as it appears on your color - coded pass-key这句话,nine-digit和pass-key中的连字符不能与之匹配,但color - coded中的连字符可以与之匹配。
用\Bi\B
去匹配上述句子,nine-digit里的3个i
都能匹配,剩下的不能匹配。
注意除了字母数字下划线,其它的字符都视为单词边界
字符串边界:匹配字符串开头用^
,结尾用$
分行匹配模式(?m)
将使得正则表达式引擎把行分隔符当作一个字符串分隔符来对待。这个模式必须出现在整个模式的最前面。比如(?m)^\w+
可以提取字符串中每行的第一个单词。(?m)\w+$
可以提取字符串中每行的最后一个单词。
不同平台的行终止符:
Unix/Linux:
Windows:
\r
Mac OS:
如果我们不想定位到字符串内部的行起始位置,只关心整个字符串的起始位置,则可以使用\A
,绝大多数工具的正则表达式都支持。(?m)\A\w+
匹配整段文本的第一个单词。
\Z
和\z
不受多行模式的影响,在任何情况下都匹配整个字符串的结束位置。\Z
等价于默认模式(非多行模式)下的$
,如果字符串的末尾有行终止符,则它匹配换行符之前的位置;\z
则不管行终止符,只匹配整个字符串的结束位置。
注意Python不支持\z
,Python的\Z
等价于其他语言中的\z
.
^
和$
进行正则表达式替换时并不会被替换。常见的应用是将纯文本转换为HTML,比如将纯文本的电子文档转换成ePub格式。最简单的思路是使用多行模式将^
替换为<p>
,将$
替换为</p>
。
^
和$
的替换
去除行首的空白字符
前后查找/环视(非捕获分组)
环视类似单词边界,在它旁边的文本需要满足某种条件,而且本身不匹配任何字符。
向前查找指定了一个必须匹配但不在结果中返回的模式。向前查找实际就是一个以?=
开头的子表达式,需要匹配的文本跟在后面。
模式.+(:)
查找到并且匹配结果包含:
,模式.+(?=:)
查找到但匹配结果不包含:
。
向前查找是查找出现在匹配文本之后的字符,但不消费那个字符。向后查找是查找出现在被匹配文本之前的字符,但不消费它。向后查找操作符是?<=
比如匹配价格。\$[0-9.]+
匹配$
开头的价格,会返回$
。(?<=\$)[0-9.]+
不会返回$
。
向前查找模式的长度是可变的,它们可以包含.
和+
之类的元字符,所以非常灵活。
向后查找模式只能是固定长度。
向前查找和向后查找可以结合起来。
对前后查找取非,即查找不与给定模式相匹配的文本,称为负向前/后查找
各种前后查找操作符:
(?=)
正向前查找(肯定顺序环视)
(?!)
负向前查找(否定顺序环视)
(?<=)
正向后查找(肯定逆序环视)
(?<!)
负向后查找(否定逆序环视)
否定的意思是如果正则表达式匹配成功,则在当前位置匹配失败。
顺序是指当前位置右侧,逆序是指当前位置的左侧。肯定是指子表达式能匹配的字符串必须出现,否定则是子表达式能匹配的字符串不能出现。
(?=\d{3})
右侧必须出现三个数字字符(?!\d{3})
右侧不能出现三个数字字符(?<=\d{3})
左侧必须出现三个数字字符(?<!\d{3})
左侧必须出现三个数字字符
比如在一段有价格和数量的文本里,I paid $30 for 100 apples
匹配价格用(?<=\$)\d+
,匹配数量用\b(?<!\$)\d+\b
准确匹配open tag:
格式化数字字符串,加入逗号的位置是:右侧的数字字符串的长度是3的倍数(且只能是3的倍数,不能有多余的数字),且左侧也是数字字符。
去掉中英文混排文本中不必要的空白字符
肯定环视要判断成功,字符串中必须有字符由环视结构中的表达式匹配;而否定环视要判断成功,却有两种情况:字符串中出现了字符,但这些字符不能由环视结构中的表达式匹配;或者字符串中不再有任何字符,也就是说,这个位置是字符串的起始位置或者结束位置。
在电子邮件地址中,进行主机名验证。主机名以点号分隔为多个域名字段,每个域名字段可以包含大小写字母、数字字母、横线,但是横线不能出现在开头位置。每个域名字段的长度最多为63个字符,整个主机名的长度最多为255个字符。
一般来说,凡是从文本中提取“有长度特征的数据”,都需要用到环视。比如准确匹配6位数字构成的字符串,(?<!\d)\d{6}(?!\d)
从26个字母中减去5个辅音字母,(?![aeiou])[a-z]
环视结构中的括号只是结构需要,并不影响捕获分组。
环视结构中出现了捕获型括号,会影响分组
环视结构中的捕获型括号一旦匹配完成,就不能回溯。
Python规定在逆序环视中的表达式能匹配的文本长度必须是固定的。(?<=dog)
和(?<=(dog|cat))
都是合法的,而(?<=dog?)
和(?<=(dog|cats))
都不合法,因为环视中的子表达式能匹配的文本长度不确定。
可以用多选结构来改写。((?<=dog)|(?<=dogs))
, ((?<=dog)|(?<=cats))
。
环视的组合
环视匹配的并不是字符,而是位置。多个环视可以组合在一起,实现同一个位置的多重判断。
环视中包含环视:(?=[-a-zA-Z0-9.]{0,255}(?![-a-zA-Z0-9.]))
保证的是整个主机名字符串的长度在255个字符以内。
并列多个环视,并列的先后顺序无所谓:查找这样的起始位置,它之后是数字字符串,不过不能以999开头。
将若干个环视作为多选分支排列在多选结构中。比如想找到这样的位置,它之后要么不是数字字符,要么是一个数字字符和一个非数字字符。总的环视就是((?!\d)|(?=\d\D))
。
嵌入条件
回溯引用条件只在前面的子表达式搜索取得成功的情况下才允许使用一个表达式。
用来定义这种条件的语法是(?(backreference)true-regex)
,其中?
表明这是一个条件,括号里的backreference是一个回溯引用,true-regex是一个只在backreference存在时才会被执行的子表达式。
例子:需要把一段文本里的<IMG>
标签全都找出来,如果某个<IMG>
标签是一个链接(被括在<A>
和</A>
标签之间)的话,还要把整个链接标签匹配出来。
否则表达式只在给定的回溯引用不存在(也就是条件没有得到满足)时才会被执行。用来定义这种条件的语法是(?(backreference)true-regex|false-regex)
下面的电话号码只有第1和第2个是合法的,我们想匹配它们:
123-456-7890
(123)456-7890
(123)-456-7890
(123-456-7890
1234567890
123 456 7890
正则表达式:(\()?\d{3}(?(1)\)|-)\d{3}-\d{4}
(\()?
匹配一个可选的左括号,(?(1)\)|-)
是一个回溯引用条件,它将根据条件是否得到满足而去匹配)
或-
:如果(1)存在,\)
必须被匹配;否则,-
必须被匹配。这样一来,只有配对出现的括号才会被匹配;如果没有使用括号或括号不配对,电话号码中的区号和其余数字之间的-分隔符必须被匹配。
前后查找条件只在一个向前查找或向后查找操作取得成功的情况下才允许一个表达式被使用。
==例子没看懂==
匹配模式
常用的匹配模式:不区分大小写模式、单行模式、多行模式、注释模式。
不区分大小写模式有两种办法指定:以模式修饰符指定,或者以预定义的常量作为特殊参数传入来指定。
模式修饰符即模式名称对应的单个字符,使用时将其填入特定结构(?modifier)
中(modifier为模式修饰符),嵌在正则表达式的开头。比如不区分大小写的匹配模式对应的模式修饰符是i
。
模式修饰符写在最开头表示整个正则表达式都指定此模式,如果出现在中间,则表示此模式从这里开始生效;如果出现在某个括号内,那么它的作用范围只限于括号内部。Python的情况不同,不管出现在哪个位置,都对整个正则表达式生效。
有另一类失效修饰符,用来终止某种模式的作用范围,(?-modifier)
。不过Python不支持这种写法。
这个表达式几乎可以原封不动的用在任何语言中,只有JavaScript例外。
第二种方式是使用预定义的常量作为参数传入正则函数。比如Python是Re类的静态成员re.IGNORECASE
点号不能匹配换行符。单行模式下所有文本似乎只在一行里,换行符变成了普通字符,点号可以匹配。
单行模式对应的模式修饰符是s。Python中的预定义常量是re.S和re.DOTALL。
单行模式影响的是点号的匹配规则。
多行模式影响是的^
和$
的匹配规则。默认模式下,它们匹配的是整个字符串的起始位置和结束位置,但在多行模式下,它们也能匹配字符串内部某一行文本的起始位置和结束位置。
模式修饰符是m,预定义常量是re.M和re.MULTILINE
注释模式。许多语言支持使用(?#comment)
的记法添加注释,comment就是注释的内容。模式修饰符是x。预定义常量是re.X和re.VERBOSE。
如果需要同时使用多种模式,只要将模式修饰符排列起来就可以了。比如(?mx)
或者将预定义常量用|
组合起来。
Python的re还包含其他模式,列举2种
re.U或re.UNICODE:此模式下,
\w
,\d
,\s
等字符组简记法的匹配规则会发生改变,比如\w
能匹配Unicode中的“单词字符”,包括中文字符,\d
也能匹配1、2之类的全角数字字符,它有对应的模式修饰符u。re.A或re.ASCII:Python3以上的版本中,正则表达式默认采用Unicode匹配规则,如果希望让
\d
,\w
等字符组简记法恢复到ASCII匹配规则,可以使用此模式,它有对应的模式修饰符a。
Python中的匹配模式
re.I re.IGNORECASE
i
不区分大小写模式
re.X re.VERBOSE
x
注释模式
re.M re.MULTILINE
m
多行模式
re.S re.DOTALL
s
单行模式
re.U re.UNICODE
u
Unicode模式
re.A re.ASCII
a
ASCII模式
转义
通常说的string中,string称为字符串文字,它是某个字符串的值在源代码中的表现形式。比如字符串文本\n,它包含\和n两个字符,意义是一个换行符。在生成字符串时,应当进行“字符串转义”,才能准确识别字符串文字中\n的意思。
在源代码中看到的“正则表达式”regex,其中的regex称为正则表达式文字,是正则表达式的表现形式。比如正则表达式\d,其正则文字包含\和d两个字符,它的意义是匹配数字字符的字符组简记法。在生成正则表达式时应当进行“正则转义”,才能将正则文字中的\d识别为字符组简记法。
不少正则表达式都是以字符串形式提供的。字符串文字首先必须经过字符串转义,才能得到真正的字符串,这个字符串作为正则文字,经过正则转义,最终得到正则表达式。
\\\\
经过字符串转义和正则转义,得到的正则表达式就是一个\
和可以不加转义字符,而\b
必须加,不然会被解析为退格符。最保险的办法是:正则表达式中的每一个\
,在字符串文字中都要写成\\
。
如果希望正则表达式是怎样的,正则文字中就怎样写,有两种办法:第一,使用原生字符串,也就是完全忽略字符串转义的特殊字符串。第二,直接使用正则文字,在Ruby和JavaScript中可以这么做。
常用结构的转义
字符组
[]
\[]
只对开方括号转义
.
\.
-
\-
[a\-b]
等价于[-ab]
量词
*
+
?
\*
\+
\?
*?
+?
??
\*\?
\+\?
\?\?
{m,n}
\{m,n}
只对开花括号转义
括号
(...)
\(...\)
开闭括号都需要转义
多选结构
`
`
|
括号和多选结构
`(...
...)`
`(...
断言
^
$
\^
\$
替换引用
$num
\$
或$$
在替换的replacement字符串中转义
消除元字符特殊含义的函数:re.escape(text)
在字符组内部,只有三个字符需要转义
闭方括号]
横线-,如果紧跟在开方括号之后也可以不用转义
^,如果不是紧跟在开方括号之后也可以不用转义
正则表达式的处理形式有函数式处理和面向对象式处理。
正则表达式中的优先级
1
(regex)
整个括号内的子表达式成为单个元素
2
* ? +
限定之前紧邻的元素
3
abc
普通拼接,元素相继出现
4
a|bc
多选结构
Unicode
在Unicode出现之前,各种系统的文字编码是各不相同的,比如中国大陆使用的是GB2312,中国台湾使用的是Big5,美国使用的是ASCII(ISO-8859-1),日本使用的是Shift JIS等。这些编码系统的作用,都是给每个字符分配一个数字(码值)作为编码,然后进行码值-字符和字符-码值的双向映射操作。
Unicode由两大部分组成,一部分是UCS(Universal Character Set),它定义了一个广阔的空间,能把各种不同语言中的字符全部装进去,为每个字符分配唯一的码值(Unicode中的术语是Code Point)。另一部分是UTF(Unicode Transformation Format),它定义了UCS中的每个码值以什么样的方式来传输(和存储)。
我们通常谈论的“Unicode编码”,严格来说是指UCS,也可以叫“字符集”,它关心每个字符对应的码值是多少。比如中文字符“正”的码值是十进制的27491,十六进制的表示是6B63。讨论Unicode时,常用的表示法是U+6B63.
码值只是概念,要变成字节才能够读取、存储、传输,所以要用到UTF。如果采用如今常用的UTF-8编码,它需要用3字节:E6 AD A3。
UCS空间设计的很大,码值从0到1114111,一共可以容纳超过100万个字符。整个UCS又可以分为17个“平面”(Planes),每个平面包含65536个码值。
常见的正则表达式的文档都是关于英文(ASCII字符)的,如果依照处理ASCII字符的方式处理中文字符,有可能出错。如果我们希望匹配“正”(GBK码值D5FD)或者“则”(GBK码值是D4F2),很自然会想到使用字符组[正则]
。
GBK编码环境下的字符组会出现错误匹配(编码环境指的是源代码和它所处理的数据文本采用的编码):
虽然用了GBK编码,每个中文字符用2字节表示,但是正则表达式处理时识别不出这是“2个多字节字符构成的字符组”,而将其视为“4个单字节字符构成的字符组”。如果按照字节来匹配,“正则”和“遭遇”恰好都有D4这个字节,所以这就是匹配结果。
Unicode编码环境下的字符组也会出现同样的错误匹配。(Python2下只要没有显式指定字符串为Unicode字符串,即便代码文件使用的是Unicode格式,仍然会按照单个字节的方式来处理)
如果我们把正则表达式和字符串都显式指定为Unicode模式,就可以彻底解决问题了。Python2的源代码的顶端要加上编码说明uft-8。
Python3中字符串默认采用Unicode编码,所以不会出现上述问题。
GBK编码作为常见的中文编码,使用确实很多,但在GBK编码环境下使用正则表达式,却可能遇到非常奇怪的现象,因为正则表达式处理程序一般只能准确识别Unicode字符。所以不推荐使用GBK编码。
如果使用正则表达式处理含有多字节字符的文本,能使用Unicode编码环境就尽量使用。
Unicode与字符组简记法
正则表达式里的\d
匹配数字字符,\w
匹配单词字符,\s
匹配空白字符。一般来说,数字字符就是[0-9]
,单词字符就是[0-9a-zA-Z]
,空白字符则包括空格、回车等字符,但这只是ASCII编码中的情况,在Unicode编码中并非如此。
因为涵盖了多种语言和字符,所以在Unicode编码中,全角数字也算作“数字字符”,可以由\d
匹配;中文字符,也可以算作“单词字符”;中文的全角空格(码值为30FF),也可以算作“空白字符”。所以,如果在Python2中指定了正则表达式使用Unicode模式(最简单的方式就是在正则表达式的开头指定模式修饰符(?u)
),\d
, \w
, \s
就能匹配全角数字、中文字符、全角空格。对于这种情况,可以称为Unicode匹配规则,之前ASCII编码中的匹配,称为ASCII匹配规则。Python3默认采用Unicode匹配规则,可以在表达式最开始用(?a)
指定ASCII模式。
Emoji
最全的Emoji列表:http://www.unicode.org/Public/emoji/5.0/emoji-data.txt
Python有个Emoji包,专门处理Emoji。
匹配原理
正则表达式采用了一种特殊的理论模型:有穷自动机(Finite Automata,也叫有穷状态自动机,finite-state machine)。这种机器具备有限个状态,可以根据不同的条件在状态之间转移。
严格说起来,有穷自动机必须满足4个条件:
具有有限多个状态
有一套状态转移函数(或者叫规则)
有一个开始状态
有一个或多个最终状态
正则表达式所使用的理论模型就是有穷自动机,其具体实现称为正则引擎(Regex Engine)。用正则表达式处理字符串,首先需要生成自动机(很多语言中使用正则表达式之前都要编译正则对象),之后,无论输入什么字符串,正则引擎都只需要老老实实地在状态之间游走。
待整理
匹配HTML的open tag
<[^/][^>]*>
子表达式一章
表达式会匹配self-closing tag,比如<br />
。修改表达式为<[^/]([^>]*[^/])?>
可以解决。这个表达式可以匹配<u>
这种tag name为单个字母的情况,也能匹配tag name的第一个字母之后不包含>
,结尾不是/
的字符串。
放到多选结构
多选结构的排列是有讲究的,比如(jeff|jeffrey)
用来匹配jeffrey,结果会是什么呢?这个问题没有标准答案,Java, .NET, Python, Ruby, JavaScript, PHP中都会优先选择最左侧的分支。在平时使用时应当尽量避免多选分支中存在重复匹配,因为这样会大大增加回溯的计算量。也就是说,应当避免这样的情况:针对多选结构(option1|option2)
,某段文本既可以由option1匹配,也可以由option2匹配。
Python回溯引用
用反向引用匹配重复字母
解析HTML代码时匹配tag:<([^>]+)>[\s\S]*?</\1>
如果tag后面有属性,例如<span class="class1">text</span>
。就要修改表达式为<([a-zA-Z0-9]+)(\s[^>]+)?>[\s\S]*?</\1>
可能具有二义性的反向引用:
\10
是表示第10个捕获分组还是第1个捕获分组之后跟着一个字符0呢?
在Python中,\10
会被解释成第10个捕获分组匹配的文本,下面的程序会报错:
如果希望效果是第1个捕获分组之后跟着一个字符0,那就要写成\g<1>0
Python引用分组(捕获分组)
容易犯错的:
在替换中使用分组
因为\1
,\2
不是字符串中的合法转义序列,所以必须指定为原生字符串,在字符串前面加一个"r"。
如果想引用整个表达式匹配的文本,不能使用\0
,因为\0
开头的转义序列通常表示用八进制形式表示的字符,\0
本身表示ASCII字符编码为0的字符。如果一定要引用整个表达式匹配的文本,则可以稍加变通,给整个表达式加上一对括号,之后用\1
来引用。
Python命名分组
标识分组为容易记忆和辨别的名字,而不是数字编号。
Python中用(?P<name>regex)
来分组。用法举例:
即便使用了命名分组,每个命名分组同时也具有数字编号。
Python中,如果使用了命名分组,在表达式反向引用时,必须使用(?P=name)
的记法,而要进行正则表达式替换,则要写作\g<name>
Python非捕获分组
有些结构并不真正匹配文本,而只负责判断在某个位置左右侧的文本是否符合要求,这种结构被称为断言(assertion)。常见的断言有三类:单词边界、行起始/结束位置、环视。
断言不匹配任何字符,只匹配位置;而反向引用只引用之前的捕获分组匹配的文本,之前捕获分组中锚点表示的位置信息,在反向引用时并不会保留下来。
如果表达式是(\bcat\b)\s+\1
,\1
所匹配的,就不只有单独出现的cat,还包括单词内部的cat(比如cate中的cat),如果要验证单词cat是否在字符串中出现了两次,正确的做法是在反向引用两端加上单词边界,变成(\bcat\b).*?\b\1\b
正则表达式的常见操作
提取:这里面的例子没有仔细看,有点复杂
验证
替换
切分
正则表达式的优化建议
1.使用缓存
使用Python的re.compile将正则表达式保存起来,反复使用,避免重复编译。
2.尽量准确地表达意图
3.避免重复匹配
4.独立出文本和锚点
常用的操作符模式总结
^
匹配字符串的开头,如^abc表示abc且在一个字符串的开头
$
匹配字符串的结尾,如abc$表示abc且在一个字符串的结尾
.
匹配任意字符,除了换行符
[...]
可以理解为"或"。对单个字符给出取值范围,例如[amk]匹配'a','m'或'k'
[^...]
不在[]中的字符:[^abc]匹配除了a,b,c之外的字符
re*
前一个字符0次或无限次扩展,如abc* 表示 ab、abc、abcc、abccc等
re+
前一个字符1次或无限次扩展 ,如abc+ 表示 abc、abcc、abccc等
re?
前一个字符0次或1次扩展 ,如abc? 表示 ab、abc
re{n}
精确匹配前面的表达式n次,例如o{2}表示匹配"oo"
re{n,}
匹配前面的表达式n次及以上
re{n,m}
匹配前面的表达式n到m次(含m),如ab{1,2}c表示abc、abbc
a|b
匹配a或b,如abc|def表示 abc、def
(re)
对正则表达式分组并记住匹配的文本,内部只能使用|操作符 ,如(abc)表示abc,(abc|def)表示abc、def
\w
匹配字母数字及下划线,等价于[A‐Za‐z0‐9_]
\W
匹配非字母数字及下划线
\s
匹配任意空白字符,等价于[\t\n\r\f]
\S
匹配任意非空字符
\d
匹配任意数字,等价于[0-9]
\D
匹配任意非数字
\A
匹配字符串开始
\Z
匹配字符串结束,如果是存在换行,只匹配到换行前的结束字符串。
\z
匹配字符串结束
\G
匹配最后匹配完成的位置。
\b
匹配一个单词边界,单词指的是数字、下划线或者字母。例如, 'er\b' 可以匹配"never" 中的 'er',但不能匹配 "verb" 中的 'er'。'\b99\b'可以匹配到'$99', '99',而不会匹配到'299'。
\B
匹配非单词边界。'er\B' 能匹配 "verb" 中的 'er',但不能匹配 "never" 中的 'er'。
Python正则表达式re库的使用
常用函数说明
调用方式:import re
re库采用raw string类型表示正则表达式,表示为:r'text',raw string是不包含对转义符再次转义的字符串;
re库的主要功能函数:
re.search()
在一个字符串中搜索匹配正则表达式的第一个位置,返回match对象re.search(pattern, string, flags=0)
使用group(num) 或 groups() 匹配对象函数来获取匹配表达式
可以用来进行数据验证,在正则表达式两端加上
\A
和\Z
,并判断返回值是否为None
re.match()
从一个字符串的开始位置起匹配正则表达式,返回match对象re.match(pattern, string, flags=0)
使用group(num) 或 groups() 匹配对象函数来获取匹配表达式
re.findall()
搜索字符串,以列表类型返回全部能匹配的子串re.findall(pattern, string, flags=0)
re.split()
将一个字符串按照正则表达式匹配结果进行分割,返回列表类型re.split(pattern, string, maxsplit=0, flags=0)
maxsplit是分隔次数,maxsplit=1 分隔一次,默认为 0,不限制次数。 如果是负数,表示不做任何切分。
re.finditer()
搜索字符串,返回一个匹配结果的迭代类型,每个迭代元素是match对象re.finditer(pattern, string, flags=0)
re.sub()
在一个字符串中替换所有匹配正则表达式的子串,返回替换后的字符串re.sub(pattern, repl, string, count=0, flags=0)
repl : 替换的字符串,也可为一个函数。 例如
def capitalize(match): return match.group(1).upper+match.group(2).lower()
count : 模式匹配后替换的最大次数,默认 0 表示替换所有的匹配。
参数说明
pattern是匹配的正则表达式
string是要匹配的字符串
flags : 正则表达式使用时的控制标记:
re.I --> re.IGNORECASE : 忽略正则表达式的大小写,
[A‐Z]
能够匹配小写字符re.M --> re.MULTILINE : 正则表达式中的^操作符能够将给定字符串的每行当作匹配开始
re.S --> re.DOTALL : 正则表达式中的.操作符能够匹配所有字符,默认匹配除换行外的所有字符
Match对象常用的方法和属性
start(n)
pos
end(n)
endpos
group(n)
groups()
span(n)
re
lastindex
string
expand(str)
re库的另一种等价用法(编译)
regex = re.compile(pattern, flags=0):将正则表达式的字符串形式编译成正则表达式对象, 供 match() 和 search() 等函数使用。
re.compile()还可以用来观察某个正则表达式的详细信息,方法是指定第二个参数为re.DEBUG。如果遇到复杂的表达式,或者不确定某个表达式的意义,可以通过它来观察。
缩进表示了表达式各结构的层级关系,而字符本身则显示为其码值的十进制表示(比如字符a的码值是十进制的97)。
re 库的贪婪匹配和最小匹配
.*
Re库默认采用贪婪匹配,即输出匹配最长的子串*?
只要长度输出可能不同的,都可以通过在操作符后增加?变成最小匹配
条件匹配
Python的正则表达式支持条件匹配。语法是(?(id/name)yes-pattern|no-pattern)
。其中id/name是对应捕获分组的名称或者编号,如果该捕获分组成功匹配文本,则后续匹配交由yes-pattern来完成,否则交由no-pattern来完成。no-pattern可以省略。
假设我们需要验证价格:如果前面没有美元符号$,则价格只能包含整数部分,否则还应当包含小数点和两位小数。
实用小例子
1.删除文本中的特殊符号
remove_nota列举了一些特殊符号,主要是中文的。接着用re.sub函数替换文本中的特殊符号为空值。
string.punctuation是英文的标点符号,remove_punctuation_map将这些标点符号转换成一个字典,key是符号,value是None。而translate()方法是根据参数给出的表转换字符串的字符,也就是说把文本中和key相同的字符替换成value。因为这里的value是None,也就是空值了。
给个例子:
另外,附加福利,如果只想去掉文本末尾的括号要怎么做呢?非常简单:
正则表达式前面的r表示原始字符串,比如r'\t'等价于'\t'。
先来看这个表达式的开头和结尾:'()$',还记得吗?
'(re)'表示对正则表达式分组并记住匹配的文本。'$'表示匹配字符串的末尾。小括号里面有两个分隔的中括号,分别是'[(]'和'[)]',也就是表示匹配左括号和右括号,这里换成'('和')'也是一样的。中间还有一个'.*?',表示匹配任意字符0次或多次,但是在能使整个匹配成功的前提下使用最少的重复(非贪婪匹配)。
关于贪婪和非贪婪可以看看这个例子:
正则表达式模式中使用到通配字,那它在从左到右的顺序求值时,会尽量“抓取”满足匹配最长字符串,在我们上面的例子里面,“.+”会从字符串的起始处抓取满足模式的最长字符,其中包括我们想得到的第一个整型字段中的大部分,“\d+”只需一位字符就可以匹配,所以它匹配了数字“4”,而“.+”则匹配了从字符串起始到这个第一位数字4之前的所有字符。
解决方式:非贪婪操作符“?”,这个操作符可以用在"*","+","?"的后面,要求正则匹配的越少越好。
去掉文本首尾的特殊符号以及指定字符:
这里用了一个循环,是为了保证去掉首尾的所有特殊字符,防止遗漏。
2.匹配文本中是否有某个特殊文本
寻找文本中出现的所有英文字母:
找到文本中出现的第一个英文字母:
寻找关键词在文本中出现的所有起始位置:
如果只想要第一个起始位置,可以用:
3.在中英文字符之间加上空格:
两个表达式是类似地,只不过第一个表达式是在英文和中文字符之间加上空格,第二个表达式是在中文和英文字符之间加上空格。我们来看第一个表达式。第一个()是匹配1个或多个英文,第二个()是匹配1个或多个中文字符,r'\1 \2'是匹配第1个分组和第2个分组,且中间加了空格。
4.自定义分隔符
比如,以数字作为分隔符:
加上()会使得分割后的列表保留数字,不加的话就不会保留。
以数字和空格作为分隔符:
看到结果是:有空格+数字,或者数字+空格的地方都被分隔开了。且分隔后将空格去掉,数字保留。
要理解这个表达式,首先需要理解几个概念:
例如:
所以(?<=\d)\s+
是查找数字后面的空格(可以是多个),\s+(?=\d)
是查找数字前面的空格(可以是多个)。
再看看另一个类似的语法?:
举例1
举例2:数字格式化
参考资料
https://www.runoob.com/regexp/regexp-syntax.html
Last updated
Was this helpful?