正则表达式
正则表达式入门
参考资料: 正则表达式必知必会(修订版) 度盘提取码:ga2g
正则表达式是用于字符串匹配和替换的语言,但是没有单独的编译器/解释器,集成在了很多语言的内部。虽然在不同的语言中使用会有一点差距,但是核心语法相同。
单字符
确定字符:正则表达式可以是区分大小写的纯文本。
任意字符:对于不确定或无意义的字符,可以使用占位符.
来代替。.
可以连续出现,每一个.
都代表任意一个字符(包括.
本身)。如果需要特指字符.
,可以使用转义字符\
,写成\.
的形式。
一组字符
例如想要得到文本中形式为”dot*“或”bot*“的四字符字符串(*代指任意字符),可以用特殊字符[]来代替,[]中填写所有的目标字符。在上例中,对应的正则表达式为[db]ot.
。d和b的顺序无影响。
字符区间:有时候,我们希望[]去匹配一系列字符,如数字0-9,大写字母A-F。虽然可以将符合要求的所有字符填入[]内,但是还可以用另一种表达。即正则表达式的连字符-
。连字符与ASCII码有关。[a-F]
会匹配ASCII在a和F之间的所有字符。[]内可以填入不止一对区间,例如a-fA-F
会匹配所有的字母。
取非匹配:[]还可以填写不希望匹配的字符。则其他字符都可以匹配。使用元字符^
。[^0-9]
将匹配所有的非数字。^
的效果作用于[]内的所有字符区间而不仅是其后一个。
元字符
正则表达式里的特殊字符,如果仅作为普通字符使用的话需要进行转义。创建的元字符:
Backspace | 换页符 | 换行符 | 回车符 | 制表符 | 垂直制表符 |
---|---|---|---|---|---|
[\b] | \f | \n | \r | \t | \v |
数字字符 | 非数字字符 | 字母数字下划线 | 非字母数字下划线 | 任何空白字符(\f\n\r\t\v,无\b) | 任何非空白字符 |
\d | \D | \w | \W | \s | \S |
正则表达式支持直接使用ASCII码来匹配字符,使用十六进制/八进制,如\x0A(十六进制,对应10)、\011(八进制,对应9)
另外,还有POXSIX字符类,可以直接代指特定的字符集合。但是并不是所有的语言都支持。例如javaScript就不支持POXISIX字符类。
字符类 | 说明 |
---|---|
[:alnum:] | 字母或数字,[a-zA-Z0-9] |
[:alpha:] | 字母,[a-zA-Z] |
[:blank:] | 空格或制表符,[\t ] |
[:cntrl:] | 控制符,ASCII0-31和127 |
[:digit:] | 数字,[0-9] |
[:graph:] | 可打印字符,不包括空格 |
[:lower:] | 小写字母[a-z] |
[:print:] | 可打印字符,包括空格 |
[:punct:] | 不属于alnum和cntrl的任意字符 |
[:space:] | 任何空白字符,包括空格[\f\n\r\t\v] |
[:upper:] | 大写字母[A-Z] |
[:xdigit] | 十六进制数字,[a-fA-F0-9] |
具体使用时,需要套上[]。即使用[[xdigit]]
去匹配。
重复匹配
在字符或字符集的后面添加+
表示重复匹配一个或多个该字符/字符集。例如使用[[:digit]]+@w+\.[a-fA-F]+
可以匹配**@**.**格式的电子邮箱。+
也是元字符之一。
在字符/字符集后面添加*
表示字符/字符集出现0次或多次的情况。
在字符/字符集后面添加?
表示字符/字符集出现0次或1次的情况。
在字符/字符集后面添加{n}
(n为数字)表示字符/字符集必须连续出现n次的情况。n也可以写成用,
分割的区间的形式。如a{2,4}
将匹配2~4个连续的字母a。[[:digit:]]{3, }
将匹配最少连续出现3次或以上的数字。
贪婪形和懒惰型:*
,+
,{n, }
将匹配尽可能多的字符。但是如果在后面加上字符?
可以将模式改为最小匹配。如写成*?
,+?
,{n, }?
位置匹配
字符边界: 当我们想匹配的字符位于特定位置时,我们使用边界来限定。如搜索cat时不想搜索到scatter,就可以使用\b
来限定开头和结尾(匹配一个非字母数字下划线的位置)。\bcat\b
将只匹配到cat。这里的\b
只匹配位置,不匹配字符。(也可以使用\B
来限定没有边界)
字符串边界: 当我们想匹配字符串的特定位置时,有两个元字符可供选择。^
,$
。^
有取非的意思,但是只有出现在字符集的开头部分时才表这一含义。当它出现在正则表达式的开头时,表示的是匹配字符串的开头。而$
会匹配字符串的末尾。(正则表达式对字符串的定义是传进来的所有字符,都当作一个字符串)。
分行匹配: 正则表达式可以使用一个元字符来改变另一个元字符的含义。在正则表达式的开头使用(?m)
将开启分行匹配模式。在这一模式下,正则表达式将使用换行符来作为字符串的划分。不再将所有的字符作为一个字符串。这时^
和$
将匹配更多的位置。(有些正则表达式不支持分行匹配)
子表达式
()
括起来的部分将作为一个整体,再匹配时被看作“一个字符”。这样,原本只能作用以一个字符/字符集的?
, *
, +
, {a,b}
等将可以作用与字符串片段。
为了增加代码的可读性,可以使用子表达式来对正则表达式做一定的划分。虽然一般情况下无影响,在有些实现方式里会影响执行效率。
或 元字符:在我们想要匹配年份的时候,我们寻找前两位时19或20的四位数字。可以使用|
表示“或”的意思。但是|
将左右两边的整体作为两个部分。因此可以使用()
来限定|
的使用范围。(19|20)\d{2}
可以达成目的。
子表达式支持嵌套。
回溯引用
子表达式的另一个重要的应用。
匹配:
在想要使用正则表达式中取寻找两个连续的相同单词时,后面的表达式需要知道前面匹配到了什么。再这种情况下,就需要使用回溯引用。例如[ ]+(\w+)[ ]+\1
。前面和后面的[ ]+
匹配若干个空格,中间匹配任意一个单词。关键在于后面的\1
。它就是回溯引用,代指正则表则式中出现的第一个子表达式。数字可以为1,2,3(第一个、第二个、第三个……一次类推)。再有些实现里,\0
可以代指整个正则表达式。
替换:
在之前的应用中,正则表达式都是用来搜索。正则表达式的另一个重要作用是替换。而在替换操作中,最基本的就是用使用回溯引用。
例如,在文本中匹配到一个邮箱,改成<A HREF="mailto:user@address.com">user@address.com</A>
(HTML)的格式。具体实现代码:
(\w+{\w\.}*@{\w\.}+\.\w+)//搜索模式表达式
<A HREF="mailto:$1">$1</A>//替换模式
正则表达式可以跨模式使用。用()
将搜索模式的表达式写成子表达式,就可以在替换模式中使用了。$1
和\1
效果相同。在不同的语言里又不同的要求。
大小写转换:
部分正则表达式可以使用元字符来改变字符的大小写。
元字符 | 说明 |
---|---|
\E | 结束\L或\U转换 |
\l | 下一个字符转小写 |
\L | \L到\E之间转小写 |
\u | 下一个转大写 |
\U | \U到\E之间转大写 |
前后查找
当我们需要去标记待匹配的文本的位置时,比如查找<>括住的文本,我本可以这样</w+>
但是,这样会多匹配出我们不需要的<>
。从然我们可以自己剔除不需要的部分,但是,使用前后查找的方法,我们可以直接返回我们需要的部分。
前后查找分为向前查找和向后查找。常见的语言都支持向前查找,而支持向后查找的就没有那么多了。
**向前查找 **
向前查找在语法上是一个以?=
开头的子表达式。例如.+(?=:)
可以在一堆网址中搜索出协议名,但是省略掉写一名后面的’:’字符。向前查找的子表达式可以出现在总表达式的任意位置。
向后查找
向后查找模式的标志是?<=
。使用方法类似,使用(?<=\$)[0-9.]+
可以匹配形如$12.34
的价格,但是会抛去$
字符。
向前查找和向后查找意味着被抛去的字符在我们需要文本的前方/后方。计算机处理的前后与我们阅读的前后顺序**相反**。因此向后查找,被抛去的部分反而在文本的前面。理解为向……之后、向……之前查找,更容易理解。
向前查找模式的长度是可变的,可以使用. +
等字符。但是向后查找的长度必须固定,不能使用重复匹配的元字符。
向前查找和向后查找可以同时使用。
正/负向前/后查找:
向前查找和向后查找默认都是正查找(positive look-ahead and positive look-behand)。还可以使用负查找(negative look-ahead and negative look-behand),标识分别是?=~
和?<=!
对正查找取非,意味着搜索不在标记位置出的文本,但是不太常用。
嵌入条件
正则表达式可以在表达式内部嵌入条件处理功能,限制匹配到文本的格式。
123-456-7890 //合法
(123)456-7890 //合法
(123)-456-7890 //合法
(123-456-7890 //不合法
1234567890 //不合法
123 456 7890 //不合法
比如在上述的6个电话号码中查找出符合特定格式的号码。
在之前已经接触过一些条件了。如:
?
:匹配一个字符/表达式,如果它存在的话?=
、?<=
:匹配前面或后面的文本,如果它存在的话
因此,条件也用?
字符来实现。正则表达式的条件有两种。一种是根据回溯引用,一种是根据前后查找。
回溯引用条件:
回溯引用的格式是((backreference)true-regex)
。括号里的backreference是一个回溯引用,后面的true-regex是一个子表达式。只有当backreference存在时,后面的true-regex才会执行。
例如,我们要从html文本中查找出所有的<img>
标签。但是如何这个标签是连接(被<a></a>
包含)的话,就把整个连接匹配出来。
([Aa]\s+[^>]+>\s*)?<[Ii][Mm][Gg]\s+[^>]+>(?(1)\s*</[Aa]>)
这个模式不解释是不容易看明白的。其中,
(<[Aa]\s+[">]+>\s*)?
将匹配一个<A>
或<a>
标签(以及<A>
或<a>
标签的任意屈性)。这个标签可有可无(因为这个子表达式的最后有一个?
)。接下来<[Ii][Mm][Gg]\s+[">]+>
匹配一个<!MG> (大小写均可)及其任意屈性。(?(1)\s*</[Aa]>)
是一个回溯引用条件, ?(1)的含义是: 如果第1个回溯引用(具体到本例,就是<A>
标签)存在, 则使用\s*</[Aa]>
继续进行匹配。
因此,可以写出解决上面电话号码匹配问题的代码。使用(\()?\d{3}(?(1)\|-)\d{3}-\d{4}
可以解决我们的问题。中间的(?(1)\)|-
在前面匹配到(
的情况下去匹配)
。否则,就去匹配-
。
前后查找条件:
前后查找条件与回溯引用条件极为接近,只是把回溯引用的编号替换成一个完整的正则表达式就可以了。
举个例子,匹配下列电话号码
11111//合法
22222//合法
33333-//不合法
44444-4444//合法
\d{5}(?(?=-)\-d{4})
在(?=-)
成功匹配到-
的情况下,才会匹配后面的-
和四位数字。
PS:不知道为什么,所有的条件匹配代码在网页端测试均没有成功,目前原因未明。
正则表达式中的中文/双字节字符
入门中的部分是参考的国外的书籍,因此案例和方法都是针对单字节的英文等字符的。\w
,.
等字符不配中文。在中文环境中使用正则表达式时,需要补充一些内容。
首先介绍一些关于非英文语系字符的”常识”:
2E80~33FFh:中日韩符号区。收容康熙字典部首、中日韩辅助部首、注音符号、日本假名、韩文音符,中日韩的符号、标点、带圈或带括符文数字、月份,以及日本的假名组合、单位、年号、月份、日期、时间等。
3400~4DFFh:中日韩认同表意文字扩充A区,总计收容6,582个中日韩汉字。
4E00~9FFFh:中日韩认同表意文字区,总计收容20,902个中日韩汉字。
A000~A4FFh:彝族文字区,收容中国南方彝族文字和字根。
AC00~D7FFh:韩文拼音组合字区,收容以韩文音符拼成的文字。
F900~FAFFh:中日韩兼容表意文字区,总计收容302个中日韩汉字。
FB00~FFFDh:文字表现形式区,收容组合拉丁文字、希伯来文、阿拉伯文、中日韩直式标点、小符号、半角符号、全角符号等。
因此,使用^[\u2E80-\u9FFF]+$
(^
和$
表示正则表达式的开始和结束)来匹配中日韩文字,(中文包括简体和繁体)。
另外,[\u4e00-\u9fa5]
也经常用来匹配中文字符。简体繁体同样适用。
同时,也可以直接使用[\x00-\xff]
来匹配所有的双字节字符。
Python 中的正则表达式
参考资料: python3.8 re库文档
在python中使用内置的re库来使用正则表达式。
首先python同样使用\
来对特殊字符进行转义,与正则表达式的转义相同,会造成一些繁琐的转义问题。如\\\\
含义时匹配一个\
字符。因此,在python中使用原生字符串书写正则表达式,格式为r"content"
。
python中使用正则表达式对象来实现搜索、替换等操作。把代表正则表达式的字符转转化为正则表达式对象需要使用函数re.compile(partern,flags=0)
。这个函数会返回编译好的正则表达式对象,第一个参数为表达式内容的字符串,第二个为可选参数,可以改变对象的模式。
常用的可选参数:
参数 | 效果 | |
---|---|---|
re.IGNORECASE/re.I | 不区分大小写匹配 | |
re.MULTILINE/re.M | 开启后,’^’匹配字符串每一行的开始,$ 匹配每一行的结束,相当于内联标记(?m) |
|
re.DOTALL/re.S | 让. 可以匹配换行符。 |
|
re.VERBOSE/re.X | 忽略表达式内不在字符集的空格,且使用不在字符集内的# 来进行注释,增加可读性 |
RE库常用函数
re.search(pattern,string,flags=0)
:在string
中按照正则表达式pattern
搜索,返回匹配到的第一个字符串的匹配对象或None
。
上面的函数等价于编译后的正则表达式对象pattern直接使用pattern.search(string)
。之后的其他函数支持同样的用法。一般来说如果一个正则表达式要多次使用,应该优先使用编译的方法。
re.match(pattern,string,flags=0)
:在string
中只能从第0个字符开始匹配。如果不匹配,返回None。匹配时返回一个匹配对象。
re.fullmatch(pattern,string,flags=0)
:要求整个字符串string
从头到尾与pattern
匹配的话返回匹配到的对象。否则返回None。
区分match
和fullmatch
。对于正则表达式-\w{3}
,匹配一个-
字符与3个字母。则在match
中,-1111
和以被搜索并返回-111
,但是在fullmatch
中会返回None。只有形如-123
的字符串在能被搜索到。
re.split(pattern,string,maxsplit=0,flags=0)
:使用pattern匹配来作为分隔标志来分割stirng字符串,返回结果是一个列表。split
的行为比较复杂:
-
如果pattern中有有括号,则结果会包含pattern。否则pattern的内容在结果中不出现;
>>> re.split(r'\W+', 'Words, words, words.') ['Words', 'words', 'words', ''] # 没有分隔符 >>> re.split(r'(\W+)', 'Words, words, words.') ['Words', ', ', 'words', ', ', 'words', '.', ''] #有分隔符
-
如果pattern会匹配到分割字符(换行,空格等),则结果会以一个空字符串开始,空字符串结束;
-
样式的空匹配只在不相邻的情况下才会分割字符串,即如果pattern匹配的内容为空,不会因为连续匹配到空而死循环,任意的两个字符之间有无穷个空字符,但是只会再一个地方分割;(最迷惑的地方,仍然不是很清晰)
# \W*会匹配0或多个非字符、数字、下划线。因此可以以空字符串为分割线。 >>>re.split(r'\W*', '...words...') ['', '', 'w', 'o', 'r', 'd', 's', '', ''] # 出现上述结果的原因:首先开头的空字符出现。,第二个空字符是被'...'划分出来的空字符, >>>re.split(r'(\W*)', '...words...') ['', '...', '', '', 'w', '', 'o', '', 'r', '', 'd', '', 's', '...', '', '', '']
-
maxsplit参数不为0的情况下。会限制最多的分割次数。剩下的字符穿一起出现在结果的最后。
re.findall(pattern,string,flags)
:找到string中不重复使用字母的所有匹配,数量大于1返回列表。
re.finditer(pattern,string,flags)
:功能同findall
,但是将结果保存在迭代器中。
re.sub(pattern, repl, string, count=0, flags=0)
:正则表达式的替换操作:
- repl为字符串时,将不重叠的匹配到的pattern替换为repl,返回替换后的字符串。
- repl还可以为单个参数的函数,pattern将替换为rele以pattern匹配到的字符串为参数的函数的返回值。
- count不为0时,最多执行count次替换。
- 样式的空匹配仅在与前一个空匹配不相邻时才会被替换,
sub('x*', '-', 'abxd')
返回'-a-b--d-'
。
re.subn(pattern, repl, string, count=0, flags=0)
:行为与re.sub
相同,但是返回一个元组(字符串, 替换次数)
。
re.escape(pattern)
:转义pattern中的特殊字符。如果你想对任意可能包含正则表达式元字符的文本字符串进行匹配,它就是有用的。
>>> print(re.escape('http://www.python.org'))
http://www\.python\.org
在 3.7 版更改: 只有在正则表达式中具有特殊含义的字符才会被转义。 因此, '!'
, '"'
, '%'
, "'"
, ','
, '/'
, ':'
, ';'
, '<'
, '='
, '>'
, '@'
和 "
”` 将不再会被转义。
匹配对象:
re.search()
,re.match()
等函数返回的不是直接的字符串,而是一个匹配对象。下面介绍几个匹配对象的常用方法和属性。
匹配对象在条件判断中会被判断为true
。
Match = re.match(pattern,string)
Match.group([group1, ...])
:返回匹配对象多代表的字符串。根据参数的不同,有不同的行为。
-
不带参数时,默认参数是一个0,返回的是全部匹配到的字符串。
-
当正则表达式中出现子表达式时,非0参数才有意义。参数的最大值为子表达式的数量(可以嵌套。
(()())
这种情况的子表达式个数为3)。使用参数i
,将返回从左往右第’i’个左括号对应的子表达式的匹配内容。填入多个参数时,返回元组。>>> re.search(r'((你)(好))',"你好").group(1) '你好' >>> re.search(r'(你(好))',"你好").group(2) '好'
-
Match对象重载了[]运算符,可以直接填入参数i。
Match.groups()
:返回所有的子表达式对应字符串的元组。
>>> re.search(r'((你)(好))',"你好").groups()
('你好', '你', '好')
Match.re
:返回产生这个实例的正则对象,这个实例是由正则对象 match()或search() 方法产生的。
Match.string
:传递到 match()或 search()的字符串。
c++中的正则表达式
在c++中使用正则表达式的常用方法有三种,各有优点。
首先是c语言中的<regex.h>
库。支持标准的正则表达式。它的各方面性能是三种方法里最快的。
其次,从c++11开始,c++支持了<regex>
库。但是目前貌似问题比较大。
最后,是以个第三方库,需要单独下载"boost/regex.hpp"
。这个库是功能最多,使用人数较多,文档资料教为齐全的。在有些方面,性能甚至比c语言的<regex.h>
库要快一点。从通用性上考虑,会优先学习这种。