正则表达式
简介
正则表达式是用来在文本中提取指定格式的字符串的一种语法,这种语法能够帮助我们减少程序中为了匹配特定格式的字符串而写出很多if-else语句。
快速了解
正则表达式最简单的方式如下所示:
代码语言:javascript复制正则:'123'
字符串: '123456789'
那么这个正则将会匹配到123,正则表达式默认从左向右匹配。为了详细展示正则表达式的匹配效果,下面使用Python语言来进行演示。
代码语言:javascript复制import re # 导入Python的正则表达式模块
rule = re.compile("123") # 正则
result = rule.match("123456789") # 使用正则在目标字符串"123456789"中进行匹配
print(result)
print(result.group()) # group()方法可以获得匹配结果
程序执行结果如下所示:
代码语言:javascript复制<re.Match object; span=(0, 3), match='123'>
123
元字符
每个元字符可以匹配目标字符串中的一个字符,元字符是构造正则表达式的一种基本元素。下表是常见的元字符,我们举几个例子来进行说明。
元字符 | 描述 |
---|---|
. | 匹配除了换行符(n)之外的任意字符 |
[] | 匹配[]里出现的任意一个字符 |
d | 匹配数字 |
D | 匹配非数字 |
^ | 匹配字符串的开始 |
$ | 匹配字符串的结束 |
w | 匹配字母,数字,下划线,汉字 |
W | 匹配和w相反的字符 |
b | 匹配单词的开始或者结束 |
s | 匹配任意空白字符 |
S | 匹配非空白字符 |
[.]中的点仅仅只是个普通的点,不是元字符。
代码语言:javascript复制rule = re.compile("...")
result = rule.match("12345")
print(result)
这段代码中正则是3个点,意味着在目标字符串"12345"中从左向右匹配3个字符(除了n)。 因此这段代码执行结果如下:
代码语言:javascript复制<re.Match object; span=(0, 3), match='123'>
如果将代码改为如下所示:
代码语言:javascript复制rule = re.compile("...")
result = rule.match("12n345")
print(result)
那么正则将无法匹配到相应的内容。程序执行结果如下所示:
代码语言:javascript复制None
需要注意的是,如果是在支持正则表达式进行查找的文本编辑器中,那么将不会有任何问题。但是在程序中会涉及到转义字符的问题。例如,匹配3.1415926。那么.的匹配就需要特殊处理。
代码语言:javascript复制rule = re.compile("3.14")
result = rule.match("3#1415926") #
print(result)
.匹配除了n之外的任意字符,那么程序将会匹配到3#14这个结果。执行结果如下所示:
代码语言:javascript复制<re.Match object; span=(0, 4), match='3#14'>
正确的方式应该如下:
代码语言:javascript复制rule = re.compile("3.14") # .
result = rule.match("3#1415926")
print(result)
下面的例子匹配0-9中的任意一个。
代码语言:javascript复制rule = re.compile("[0-9]")
result = rule.match("56454686")
print(result)
程序执行结果如下:
代码语言:javascript复制<re.Match object; span=(0, 1), match='5'>
一般我们在一个网站注册账号的时候,让你输入邮箱或者手机号,就可以使用这样的方式来检查输入的格式是否正确。例如:
代码语言:javascript复制rule = re.compile("1[3458]dddddddd")
result = rule.match("15936933468")
print(result)
这样就可以成功匹配13,14,15,18开头的手机号了。
重复限定符
前面的规则中,我们写了8个d,这其实并不友好。不过正则表达式提供了重复限定符,来减少重复元字符。
重复限定符 | 描述 |
---|---|
* | 0次或者无限次 |
| 1次或者无限次 |
? | 0次或者1次 |
{n} | 重复n次 |
{n,} | 重复至少n次 |
{n,m} | 重复n——m次 |
先来看一个特殊例子,匹配任意多个任意字符。
代码语言:javascript复制import re
rule = re.compile(".*") # 匹配任意个任意字符
result = rule.match("123")
print(result)
result = rule.match("ABIDE")
print(result)
result = rule.match("154adv实打实❀")
print(result)
程序执行结果如下所示:
代码语言:javascript复制<re.Match object; span=(0, 3), match='123'>
<re.Match object; span=(0, 5), match='ABIDE'>
<re.Match object; span=(0, 10), match='154adv实打实❀'>
下面是一个实际的例子,邮箱格式匹配。
代码语言:javascript复制import re
rule = re.compile("[0-9a-zA-Z_]{3,30}@.{1,30}..{1,30}")
result = rule.match("12345@qq.com")
print(result)
result = rule.match("udsa_ad231@163.com")
print(result)
程序执行结果如下所示:
代码语言:javascript复制<re.Match object; span=(0, 12), match='12345@qq.com'>
<re.Match object; span=(0, 18), match='udsa_ad231@163.com'>
再来看另外一个实际的例子,例如,我们要匹配159开头的手机号。
代码语言:javascript复制rule = re.compile("^159d{8}$") # ^159匹配以159开头,d{8}$匹配以8位数字结束
result = rule.match("15986532544")
print(result)
result = rule.match("159865325444")
print(result)
result = rule.match("1598653254")
print(result)
程序执行结果如下所示:
代码语言:javascript复制<re.Match object; span=(0, 11), match='15986532544'>
None
None
正则表达式里以什么开头和以什么结尾还是很重要的,不过python的match()方法默认包含了^,也就是以什么开始,但是没有包括以什么结束。
分组
下面是正则表达式中常见的分组符号。
分组符 | 描述 |
---|---|
| | 代表或者含义 |
() | 将括号作为一个分组 |
num | 引用分组索引 |
(?: ) | 不补获分组里面的值 |
下面是一些例子,展示了分组的用法。
代码语言:javascript复制rule = re.compile(r"<(w )>. </(1)>") # 1表示匹配前面第一个分组中的内容
result = rule.match("<head>哈哈</head>")
print(result)
result = rule.match("<head>哈哈</body>")
print(result)
程序执行结果如下所示:
代码语言:javascript复制<re.Match object; span=(0, 15), match='<head>哈哈</head>'>
None
继续下一个例子。
代码语言:javascript复制rule = re.compile(r"(CVE|cve)-d{4}-d{4,5}")
result = rule.match("CVE-2020-1569")
print(result)
result = rule.match("cve-2021-35696")
print(result)
程序执行结果如下所示:
代码语言:javascript复制<re.Match object; span=(0, 13), match='CVE-2020-1569'>
<re.Match object; span=(0, 14), match='cve-2021-35696'>
当然了,正则表达式中的值是可以被提取出来的,例如:
代码语言:javascript复制rule = re.compile(r"(CVE|cve)-d{4}-d{4,5}")
result = rule.match("CVE-2020-1569")
print(result.group()) # 获取值
result = rule.match("cve-2021-35696")
print(result.group(1)) # 获取第一个分组的值
程序执行结果如下所示:
代码语言:javascript复制CVE-2020-1569
cve
贪婪模式
贪婪模式就是尽可能多的去匹配字符串。python的re默认是贪婪模式,例如:
代码语言:javascript复制s = "<div>1</div><div>2</div>"
rule = re.compile(r"<div>. </div>") # python默认是贪婪模式
result = rule.match(s)
print(result)
程序执行结果如下所示:
代码语言:javascript复制<re.Match object; span=(0, 24), match='<div>1</div><div>2</div>'>
匹配到了整个两个div标签,有时候,我们并不想让他在贪婪模式下进行匹配,那么就需要控制它匹配第一个div,此时可以使用?来启用非贪婪模式。如下所示:
代码语言:javascript复制s = "<div>1</div><div>2</div>"
rule = re.compile(r"<div>. ?</div>") # python默认是贪婪模式
result = rule.match(s)
print(result)
程序执行结果如下所示:
代码语言:javascript复制<re.Match object; span=(0, 12), match='<div>1</div>'>
可以看到,这样就只会匹配到第一个div标签。
search()和findall()
search函数不管匹配的字符串在哪儿(无需从最左边开始),只要满足正则即可。例如:
代码语言:javascript复制s = "中国960万,14亿人"
rule = re.search(r"(d ).*?(d )", s)
print(rule.group(1))
print(rule.group(2))
程序执行结果:
代码语言:javascript复制960
14
search函数只能返回一个结果,所以上面是根据分组来打印的值。 findall函数则更加的方便,他可以从任意位置开始寻找满足的正则。很方便全文查找某一特定格式的内容。例如:
代码语言:javascript复制s = "中国960万,14亿人"
rule = re.compile(r"d ")
result = rule.findall(s)
print(result)
程序执行结果如下:
代码语言:javascript复制['960', '14']
请注意,findall返回的结果是一个列表。
split()和sub()
代码语言:javascript复制split()的用法如下:
代码语言:javascript复制s = "中国960万,14亿人"
rule = re.compile(r"d ")
result = rule.split(s) # 分割
print(result)
result = rule.split(s, 1) # 参数1是原始字符串,参数2是进行匹配的次数。
print(result)
程序执行结果如下所示:
代码语言:javascript复制['中国', '万,', '亿人']
['中国', '万,14亿人']
sub()函数的功能是替换,它在实际中的用处可能非常多。例如:
代码语言:javascript复制rule = re.compile(r"d ")
result = rule.sub('1065', s) # 参数1是替换掉正则匹配到的内容,参数2是原始字符串
print(result)
result = rule.sub('1065', s, 1) # 参数1是替换掉正则匹配到的内容,参数2是原始字符串, 参数3是匹配次数
print(result)
程序执行结果如下所示:
代码语言:javascript复制中国1065万,1065亿人
中国1065万,14亿人
实际上,参数1可以是函数。例如:
代码语言:javascript复制def fun(matched): # 该函数必须有一个参数,这个参数用来接受匹配的结果。
tmp = matched.group()
tmp = int(tmp) 1
return str(tmp)
result = rule.sub(fun, s)
print(result)
程序执行结果如下所示:
代码语言:javascript复制中国961万,15亿人
到这里关于正则表达式入门的东西基本已经介绍完了。如果不遇到过于难的问题,本篇应该就足够了。