正则表达式入门
Time: 2020-02-29 Tags: miscLink: 正则表达式入门
最简单的正则表达式
在一段文本中查找high这个单词的时候,high这个单词就是个正则表达式。它可以精确匹配到以下内容:由四个字符组成,第一个是h,后面依次是igh。如果要精确查找到high这个单词,则需要写:\bhigh\b
\b被称作一个元字符。代表单词单词的开头结尾或者是分界处。只匹配一个位置。
你想查找high后面不远处有个”到不行了”,应该用\bhigh\b.*\b到不行了\b
。
这里的.是一个元字符,代表除了换行符之外的任意一个字符。*也是一个元字符,代表*前边的内容可以重复多次以使得整个表达式得到匹配。
所以,\bhigh\b.*\b到不行了\b
的意思就是先是一个单词high,然后是任意个非换行符的字符,最后是到不行了这四个字。
0\d\d-\d\d\d\d\d\d\d\d
,这个表达式表示以0开头,然后是两个数字,然后是一个连接符“-”,然后是8个数字。
\d是一个元字符,匹配一个数字。
为了避免重复,我们可以这么写:0\d{2}-\d{8}
。
更多的元字符
代码 | 说明 |
---|---|
. | 匹配除换行符以外的任意字符 |
\w | 匹配字母或数字或下划线或汉字 |
\s | 匹配任意的空白符 |
\d | 匹配数字 |
\b | 匹配单词的开始或结束 |
^ | 匹配字符串的开始 |
$ | 匹配字符串的结束 |
\s匹配空白符,包括空格,制表符,换行符,中文全角空格等。
\w匹配字母或数字或下划线或汉字。
example:
\ba\w*\b
,以a开头的单词a后有任意数量的字符或数字\d+
,匹配一个或者多个数字。+和*差不多,*可以是0次,+至少一次。\b\w{6}\b
六个字符组成的单词
\^和$匹配一个字符串的开头和结尾。例如输入的QQ号必须为5位到12位时,可以写^\d{5,12}$
,其中{5,12}表示不多于12,不少于5。
字符转义
查找.的话可以写\.
重复
代码/语法 | 说明 |
---|---|
* | 重复零次或更多次 |
+ | 重复一次或更多次 |
? | 重复零次或一次 |
{n} | 重复n次 |
{n,} | 重复n次或更多次 |
{n,m} | 重复n到m次 |
字符类
预定义:[.?!]代表匹配点号或者问好或者叹号。[aeiou]匹配元音字母。
[0-9]与\d功能一致,[a-z0-9A-Z_]与\w功能一致(只考虑英文)。
一个例子:\(?0\d{2}[)-]?\d{8}
这个正则表达式可以匹配几种格式的电话号码,像(010)88886666,或者022-22334455,或者011-45141145
分枝条件
0\d{2}-\d{8}|0\d{3}-\d{7}
表示-前三位,-后八位,或者-前四位,-后七位。
匹配成功的案例:010-12345678,0536-1234567。
分组
(\d{1,3}\.){3}\d{1,3}
用来匹配一个ip地址,表示三位数字加一个点的组合重复三次再匹配三位数字,但是无法检测合法性。例如:256.300.888.999
((2[0-4]\d|25[0-5]|[01]?\d\d?)\.){3}(2[0-4]\d|25[0-5]|[01]?\d\d?)
这个正则表达式虽然很长却可以检测正确性。
反义
代码/语法 | 说明 |
---|---|
\W | 匹配任意不是字母,数字,下划线,汉字的字符 |
\S | 匹配任意不是空白符的字符 |
\D | 匹配任意非数字的字符 |
\B | 匹配不是单词开头或结束的位置 |
[^x] | 匹配除了x以外的任意字符 |
[^aeiou] | 匹配除了aeiou这几个字母以外的任意字符 |
就是大写或者加上^符来不匹配某些字符。
后向引用
\b(\w+)\b\s+\1\b
,其中(\w+)建立了一个组,并且命名为分组1,后面的\1表示(\w+),而且两个内容一样。比如go go,jo jo这样的单词。
\b(?<Word>\w+)\b\s+\k<Word>\b
,自己规定组名。
分类 | 代码/语法 | 说明 |
---|---|---|
(exp) | 匹配exp,并捕获文本到自动命名的组里 | |
捕获 | (? | 匹配exp,并捕获文本到名称为name的组里,也可以写成(?’name’exp) |
(?:exp) | 匹配exp,不捕获匹配的文本,也不给此分组分配组号 | |
(?=exp) | 匹配exp前面的位置 | |
零宽断言 | (?<=exp) | 匹配exp后面的位置 |
(?!exp) | 匹配后面跟的不是exp的位置 | |
(?<!exp) | 匹配前面不是exp的位置 | |
注释 | (?#comment) | 这种类型的分组不对正则表达式的处理产生任何影响,用于提供注释让人阅读 |
零宽断言
(?=exp)
也叫零宽度正预测先行断言,断言自身出现的位置的后面能匹配表达式exp。
\b\w+(?=ing\b)
匹配以ing结尾的单词ing前面的部分。I’m singing while you’re dancing,匹配结果为sing,danc。
(?<=exp)
也叫零宽度正回顾后发断言,它断言自身出现的位置的前面能匹配表达式exp。
(?<=\bre)\w+\b
匹配以re开头re以后的部分,reading a book匹配结果为ading。
((?<=\d)\d{3})+\b
对1234567890匹配,匹配结果为234567890。
原因是表达式最后有一个\b,所以从结尾开始数,数三位数,前面的那一位就不匹配,然后再从刚刚位置往前数,当前面不满三位数的时候停止。所以三次分别数出890、567、234。
负向零宽断言
零宽度负预测先行断言(?!exp)
,断言此位置的后面不能匹配表达式exp。
\d{3}(?!\d)
匹配三位不以数字为后续的数字,\b((?!abc)\w)+\b
匹配没有abc这个连续字符串的单词。
零宽度负回顾后发断言(?<!exp)
,断言此位置的前面不能匹配表达式exp。
(?<![a-z])\d{7}
匹配前面不是小写字母的七位数字。
(?<=<(\w+)>).*(?=<\/\1>)
匹配html里面的标签。例如<b></b>
这样的。
注释
通过(?#comment)
来包含注释。
2[0-4]\d(?#200-249)|25[0-5](?#250-255)|[01]?\d\d?(?#0-199)
我们可以前面的一个表达式写成这样:
(?<= # 断言要匹配的文本的前缀
<(\w+)> # 查找尖括号括起来的内容
# (即HTML/XML标签)
) # 前缀结束
.* # 匹配任意文本
(?= # 断言要匹配的文本的后缀
<\/\1> # 查找尖括号括起来的内容
# 查找尖括号括起来的内容
) # 后缀结束
贪婪与懒惰
当正则表达式中包含能接受重复的限定符时,通常的行为是(在使整个表达式能得到匹配的前提下)匹配尽可能多的字符。以这个表达式为例:a.*b,它将会匹配最长的以a开始,以b结束的字符串。如果用它来搜索aabab的话,它会匹配整个字符串aabab。这被称为贪婪匹配。
有时,我们更需要懒惰匹配,也就是匹配尽可能少的字符。前面给出的限定符都可以被转化为懒惰匹配模式,只要在它后面加上一个问号?。
a.*?b匹配最短的,以a开始,以b结束的字符串。如果把它应用于aabab的话,它会匹配aab(第一到第三个字符)和ab(第四到第五个字符)。
代码/语法 | 说明 |
---|---|
*? | 重复任意次,但尽可能少重复 |
+? | 重复1次或更多次,但尽可能少重复 |
?? | 重复0次或1次,但尽可能少重复 |
{n,m}? | 重复n到m次,但尽可能少重复 |
{n,}? | 重复n次以上,但尽可能少重复 |
平衡组/递归匹配
xx <aa <bbb> <bbb> aa> yy
这样的字符串匹配,其中尖括号是嵌套的。
- (?’group’) 把捕获的内容命名为group,并压入堆栈(Stack)
- (?’-group’) 从堆栈上弹出最后压入堆栈的名为group的捕获内容,如果堆栈本来为空,则本分组的匹配失败
(?(group)yes no) 如果堆栈上存在以名为group的捕获内容的话,继续匹配yes部分的表达式,否则继续匹配no部分 - (?!) 零宽负向先行断言,由于没有后缀表达式,试图匹配总是失败
< #最外层的左括号
[^<>]* #它后面非括号的内容
(
(
(?'Open'<) #左括号,压入"Open"
[^<>]* #左括号后面的内容
)+
(
(?'-Open'>) #右括号,弹出一个"Open"
[^<>]* #右括号后面的内容
)+
)*
(?(Open)(?!)) #最外层的右括号前检查
#若还有未弹出的"Open"
#则匹配失败
> #最外层的右括号
衡组的一个最常见的应用就是匹配HTML,下面这个例子可以匹配嵌套的<div>标签:<div[^>]*>[^<>]*(((?'Open'<div[^>]*>)[^<>]*)+((?'-Open'</div>)[^<>]*)+)*(?(Open)(?!))</div>
在python中的引用
Python提供了re模块来支持正则表达式相关操作
函数 | 说明 |
---|---|
compile(pattern, flags=0) | 编译正则表达式返回正则表达式对象 |
match(pattern, string, flags=0) | 用正则表达式匹配字符串 成功返回匹配对象 否则返回None |
search(pattern, string, flags=0) | 搜索字符串中第一次出现正则表达式的模式 成功返回匹配对象 否则返回None |
split(pattern, string, maxsplit=0, flags=0) | 用正则表达式指定的模式分隔符拆分字符串 返回列表 |
sub(pattern, repl, string, count=0, flags=0) | 用指定的字符串替换原字符串中与正则表达式匹配的模式 可以用count指定替换的次数 |
fullmatch(pattern, string, flags=0) | match函数的完全匹配(从字符串开头到结尾)版本 |
findall(pattern, string, flags=0) | 查找字符串所有与正则表达式匹配的模式 返回字符串的列表 |
finditer(pattern, string, flags=0) | 查找字符串所有与正则表达式匹配的模式 返回一个迭代器 |
purge() | 清除隐式编译的正则表达式的缓存 |
re.I / re.IGNORECASE | 忽略大小写匹配标记 |
re.M / re.MULTILINE | 多行匹配标记 |
例子:
pattern = re.compile(r'(?<=\D)1[34578]\d{9}(?=\D)')
sentence = '''
重要的事情说8130123456789遍,我的手机号是13512346789这个靓号,
不是15600998765,也是110或119,王大锤的手机号才是15600998765。
'''
# 1.
mylist = re.findall(pattern, sentence)
print(mylist)
# 2.
for temp in pattern.finditer(sentence):
print(temp.group())
# 3.
m = pattern.search(sentence)
while m:
print(m.group())
m = pattern.search(sentence, m.end())
sentence = '你丫是傻叉吗? 我操你大爷的. Fuck you.'
purified = re.sub('[操肏艹草曹]|fuck|shit|傻[比屄逼叉缺吊屌]|煞笔','*', sentence, flags=re.IGNORECASE)
username = input('请输入用户名: ')
m1 = re.match(r'^[0-9a-zA-Z_]{6,20}$', username)
poem = '窗前明月光,疑是地上霜。举头望明月,低头思故乡。'
sentence_list = re.split(r'[,。, .]', poem)
while '' in sentence_list:
sentence_list.remove('')