什么是正则表达式
正则表达式也叫做匹配模式(Pattern),它由一组具有特定含义的字符串组成,通常用于匹配和替换文本。
正则表达式,是一个独立的技术,很多编程语言支持正则表达式处理。
Wiki:正则表达式(英语:Regular Expression、regex或regexp,缩写为RE),也译为正规表示法、常规表示法,在计算机科学中,是指一个用来描述或者匹配一系列符合某个句法规则的字符串的单个字符串。在很多文本编辑器或其他工具里,正则表达式通常被用来检索和/或替换那些符合某个模式的文本内容。许多程序设计语言都支持利用正则表达式进行字符串操作。例如,在Perl中就内建了一个功能强大的正则表达式引擎。正则表达式这个概念最初是由Unix中的工具软件(例如sed和grep)普及开的。正则表达式通常缩写成“regex”,单数有regexp、regex,复数有regexps、regexes、regexen。
通常情况下,如果一个文本中出现了多个匹配,正则表达式返回第一个匹配,如果将 global 设置为 true,则会返回全部匹配;匹配模式是大小写敏感的,如果设置了 ignore case 为 true,则将忽略大小写区别。
有时侯正则表达式也简称为表达式、匹配模式或者模式,它们可以互换。 这里默认 global 和 ignore case 均为 true。
正则表达式历史
正则表达式的“鼻祖”或许可一直追溯到科学家对人类神经系统工作原理的早期研究。美国新泽西州的Warren McCulloch和出生在美国底特律的Walter Pitts这两位神经生理方面的科学家,研究出了一种用数学方式来描述神经网络的新方法,他们创新地将神经系统中的神经元描述成了小而简单的自动控制元,从而作出了一项伟大的工作革新。
在1956 年,出生在被马克·吐温(Mark Twain)称为“美国最美丽的城市之一的”哈特福德市的一位名叫Stephen Kleene的数学科学家,他在Warren McCulloch和Walter Pitts早期工作的基础之上,发表了一篇题目是《神经网事件的表示法》的论文,利用称之为正则集合的数学符号来描述此模型,引入了正则表达式的概念。正则表达式被作为用来描述其称之为“正则集的代数”的一种表达式,因而采用了“正则表达式”这个术语。
之后一段时间,人们发现可以将这一工作成果应用于其他方面。Ken Thompson就把这一成果应用于计算搜索算法的一些早期研究,Ken Thompson是 Unix的主要发明人,也就是大名鼎鼎的Unix之父。Unix之父将此符号系统引入编辑器QED,然后是Unix上的编辑器ed,并最终引入grep。Jeffrey Friedl 在其著作“Mastering Regular Expressions (2nd edition)”中对此作了进一步阐述讲解,如果你希望更多了解正则表达式理论和历史,推荐你看看这本书。
自此以后,正则表达式被广泛地应用到各种UNIX或类似于UNIX的工具中,如大家熟知的Perl。Perl的正则表达式源自于Henry Spencer编写的regex,之后已演化成了pcre(Perl兼容正则表达式Perl Compatible Regular Expressions),pcre是一个由Philip Hazel开发的、为很多现代工具所使用的库。正则表达式的第一个实用应用程序即为Unix中的 qed 编辑器。
然后,正则表达式在各种计算机语言或各种应用领域得到了广大的应用和发展,演变成为目前计算机技术森林中的一只形神美丽且声音动听的百灵鸟。
以上是关于正则表达式的起源和发展的历史描述,到目前正则表达式在基于文本的编辑器和搜索工具中依然占据这一个非常重要的地位。
在最近的六十年中,正则表达式逐渐从模糊而深奥的数学概念,发展成为在计算机各类工具和软件包应用中的主要功能。不仅仅众多UNIX工具支持正则表达式,近二十年来,在WINDOW的阵营下,正则表达式的思想和应用在大部分 Windows 开发者工具包中得到支持和嵌入应用!从正则式在Microsoft Visual Basic 6 或 Microsoft VBScript到.NET Framework中的探索和发展,WINDOWS系列产品对正则表达式的支持发展到无与伦比的高度,目前几乎所有 Microsoft 开发者和所有.NET语言都可以使用正则表达式。如果你是一位接触计算机语言的工作者,那么你会在主流操作系统(*nix[Linux, Unix等]、Windws、HP、BeOS等)、目前主流的开发语言(python、PHP、C#、Java、C++、VB、Javascript、Ruby等)数以亿万计的各种应用软件中,都可以看到正则表达式优美的舞姿。
为什么要使用正则表达式
在软件开发过程中,经常会涉及到大量的关键字等各种字符串的操作,使用正则表达式能很大程度的简化开发的复杂度和开发的效率,所以在Python中正则表达式在字符串的查询匹配操作中占据很重要的地位
Python中的re模块
import re # 导入re模块
str = "mayun" # 定义一个字符串等于mayun
#查看"mayun is very good shangren"是否以mayun开头,结果保存到res中
res = re.match(str,"mayun is very good shangren")
# 使用res.group()提取匹配结果,如果是,则返回匹配对象(Match Object),否则返回None,
res.group() # 将匹配的结果显示
结果如下:
'mayun'
注意:re.match()方法匹配的是以xxx开头的字符串。
元字符
我们发现,虽然Python为外面提供了re模块供我们使用,但是功能太弱,远远无法满足我们的使用,所以我们需要继续学习正则的其他知识。
首先我们来看看正则的单个字符的匹配是如何完成的。
代码 | 说明 |
---|---|
. | 匹配出换行符(\n)以外的任意字符 |
\w | 匹配大小写字母或数字或下划线或汉字0-9、a-z、A-Z、_(下划线)、汉字和其他国家的语言符号 |
\W | 匹配非字母或数字或下划线或汉字,跟\w正好相反 |
\s | 匹配任意的空白符 |
\S | 匹配任意非空白符 |
\d | 匹配数字 |
\D | 匹配非数字 |
\b | 匹配单词的开始或结束 |
^ | 匹配字符串的开始 |
$ | 匹配字符串的结束 |
[] | 匹配[]中列举的字符 |
实例如下
re.match(".","A")
re.match(".","8")
re.match(".","_")
re.match(".","liushuaige")
re.match(".","10086")
re.match(".","\n10086") # 注意,在正则中\n(换行键是无法匹配的)
re.match(".","\t10086")
re.match(".....","10086")
# 匹配数字
re.match("[123456789]"," 6这个真是一个悲伤的故事 ")
re.match("[0-9]"," 6这个真是一个悲伤的故事 ")
re.match("[a-z]","this is good day")
re.match("[a-z0-9A-Z]"," this is good day")
re.match("[a-z0-9A-Z刘建宏]","建this is good day")
# 匹配所有中文
re.match("[\u4e00-\u9fa5]","大幅的发生")
re.match("我今年5岁了","我今年5岁了")
re.match("我今年\d岁了","我今年55岁了") # error 以为\d只会匹配一个数字
re.match("我今年\d岁了","我今年55岁了")
re.match("\D","我今年55岁了")
re.match("\D*","我今年55岁了")
re.match("\s"," 我今年55岁了")
re.match("\s","\t我今年55岁了")
re.match("\s","\n我今年55岁了")
re.match("\s","\r我今年55岁了")
字符转义
如果你想查找元字符本身的话,比如你查找.,或者*,就出现了问题:你没法指定它们,因为它们会被解释成其它的意思。这时你就必须使用 \ 来取消这些字符的特殊意义。因此,你应该使用.和*。当然,要查找 \ 本身,你也得用\
如:c:\d\e\f.txt
e.match("c:\\","c:\\a\\b") # error 报错,因为\\转义后就变成了\了
re.match("c:\\\\","c:\\a\\b") #正确
re.match(r"c:\\","c:\\a\\b") # 正确,表示匹配的字符不进行转义
#【店家推荐】,以后匹配符不要再写出上面的的转义了。
重复
在字符串的匹配中,不可避免的会出现重复字符,我们如何进行很友好的匹配呢?
常用的限定符
代码/语法 | 说明 |
---|---|
* | 重复零次或更多次 |
+ | 重复一次或者多次 |
? | 重复零次或者一次 |
{n} | 重复n次 |
{n,} | 重复n次或者更多次 |
{n,m} | 重复n到m次 |
面是一些使用重复的例子:
Windows\d+匹配Windows后面跟1个或更多数字
1[356789]\d{9}匹配以13后面跟9个数字(中国的手机号)
^\w+匹配一行的第一个单词(或整个字符串的第一个单词,具体匹配哪个意思得看选项设置)
re.match("[A-Z][a-z]*","Liujianhong ")
re.match("[A-Za-z]*","LiuJianhong ")
re.match("[A-Z][a-z]*","LiuJianhong ")
re.match("[a-zA-Z_]+[\w_]*","LiuJianhong3 ")
re.match("[0-9]?[\d]","1")
re.match("[0-9]?[\d]","111")
re.match("\w{5}","1sadfasdf11")
re.match("\w{5,}","1sadfasdf11")
re.match("\w{5,9}","1sadfasdf11")
re.match(".*\\bliu\\b","i is liu fas fsa5 662 2a") # 注意为什么使用点
re.match(r".*\bliu\b","i is liu fas fsa5 662 2a") #结果同上
反义
有时需要查找不属于某个能简单定义的字符类的字符。比如想查找除了数字以外,其它任意字符都行的情况,这时需要用到反义:
代码/语法 | 说明 |
---|---|
\W | 匹配任意不是字母,数字,下划线,汉字的字符 |
\S | 匹配任意不是空白符的字符 |
\D | 匹配任意非数字的字符 |
\B | 匹配不是单词开头或结束的位置 |
[^x] | 匹配除了x以外的任意字符 |
[^aeiou] | 匹配除了aeiou这几个字母以外的任意字符 |
案例如下:
re.match("[^abcd]","a") #表示除a、b、c、d外的任何字符都匹配
re.match("[^abcd]","b")
re.match("[^abcd]","e")
分组
我们已经提到了怎么重复单个字符(直接在字符后面加上限定符就行了);但如果想要重复一个字符串又该怎么办?你可以用小括号来指定子表达式(也叫做分组),然后你就可以指定这个子表达式的重复次数了,你也可以对子表达式进行其它一些操作。
案例如下
re.match("100|[1-9][0-9]|[0-9]","2") # 匹配100以内的数
re.match("100|[1-9]?\d$","2")
re.match("\d+(183|192|168)\s","452183 ")
re.match("\d+(183|192|168)\s","452183 ").group()
re.match("\d+(183|192|168)\s","452183 ").group(1) #注意当输入1的结果
re.match("\d+(183|192|168)\.(li|wang|liu)","452168.wang").group(2)
re.match("(.*)-(\d+)","0931-5912872 ").group(2) #小括号的使用场景,特别方便
re.match("<[a-zA-Z]*>\w*</[a-zA-Z]*>","<a>liujianhong</a>") #看似正确,其实Error
re.match("<[a-zA-Z]*>\w*</[a-zA-Z]*>","<a>liujianhong</html>") #这个就是漏洞
re.match(r"<([a-zA-Z] *)>\w*</ \1>","<a>liujianhong</html>")
#此时\1表示第一个括号中的值
re.match("<([a-zA-Z] *)>\w*</ \\1>","<a>liujianhong</html>") #这样也行
练习:匹配出163或者162或者qq或者gmail或者sohu邮箱地址,且@之前有4到20为字符
后向引用(了解)
后向引用用于重复搜索前面某个分组匹配的文本。例如,\1代表分组1匹配的文本。最好还是拿例子来说明吧:
re.match(r"<(\w*)><(\w*)>.*</\2></\1>",
"<a><span>liujianhong</span></a>")
re.match(r"<(?P<n1>\w*)><(?P<n2>\w*)>.*</(?P=n2)></(?P=n1)>",
"<a><span>liujiang</span></a>")
注意:?P<name> 和?P=name中的P必须大写。
re模块的高级应用
1、search
执行正则表达式搜索并且在搜索结束后返回所匹配到的串,只返回第一次匹配到的结果
求出帖子阅览的次数:
>>> import re
>>> res = re.search(r"\d+","当前浏览量是8808次")
>>> res.group()
'8808'
2、findall
匹配所有的对象,返回一个列表
>>> re.findall(r"\d+","1 当前浏览量是8808次,回帖人数是200人")
['1', '8808', '200']
3、sub
实现查找替换
>>> re.sub(r"\d+","1024","当前浏览量是8808次")
'当前浏览量是1024次'
还可使用函数来操作匹配到的对象
def add(temp):
return str(int(temp.group())+1)
re.sub(r"\d+",add,"当前浏览量是8808次,200")
结果为:'当前浏览量是8809次,201'
练习:将一段HTML源代码中的标签取消:
提示:re.sub(r"</?\w*>","",s)
4、split
分割字符串,结果返回列表
a = re.split('\.','www.baidu.com')
5、finditer
返回一个迭代器iterator,这个iterator yield match objects.返回顺序、内容和re.findall()相同
例如:
re.finditer('[a-n]', 'liujianhong')
<callable_iterator at 0x7f6c58629e10>
6、compile
compile用来编译正则表达式模式字符串,并生成Regular Expression Objects。
compile(pattern, flags=0)
Compile a regular expression pattern, returning a pattern object.
flags有很多可选值:
re.I(IGNORECASE)忽略大小写,括号内是完整的写法
re.M(MULTILINE)多行模式,改变^和$的行为
re.S(DOTALL)点可以匹配任意字符,包括换行符
re.L(LOCALE)做本地化识别的匹配,不推荐使用
re.U(UNICODE) 使用\w \W \s \S \d \D使用取决于unicode定义的字符属性。在python3中默认使用该flag
re.X(VERBOSE)冗长模式,该模式下pattern字符串可以是多行的,忽略空白字符,并可以添加
import re
str = "LIjianhongJgd"
p = re.compile("Li",re.I) # 表示li或略大小写,即LI、li、Li、lI都能匹配
p.match(str)
结果如下:
<_sre.SRE_Match object; span=(0, 2), match='LI'>
7、贪婪与懒惰
在Python中正则默认是贪婪模式(个别语言中也可能是非贪婪模式),贪婪模式就是总会尝试匹配更多的字符。
非贪婪模式则反之,总是尝试匹配尽可能少的字符。
在*、?、+、{m,n}后面加上?,可以将贪婪模式变成非贪婪模式。
惰性限制符
代码/语法 | 说明 |
---|---|
*? | 重复任意次,但尽可能少重复 |
+? | 重复1次或更多次,但尽可能少重复 |
?? | 重复0次或1次,但尽可能少重复 |
{n,m}? | 重复n到m次,但尽可能少重复 |
{n,}? | 重复n次以上,但尽可能少重复 |
案例如下:
>>> re.match(r"this .*","this is a number 56-89-89-458-12531")
<_sre.SRE_Match object; span=(0, 35), match='this is a number 56-89-89-458-12531'>
>>> re.match(r"this .*?","this is a number 56-89-89-458-12531")
<_sre.SRE_Match object; span=(0, 5), match='this '>
>>> re.match(r".+(\d+-\d+)","this is a number 56-89-89-458-12531")
结果为:
<_sre.SRE_Match object; span=(0, 35), match='this is a number 56-89-89-458-12531'>
>>> re.match(r".+(\d+-\d+?)","this is a number 56-89-89-458-12531")
结果为:
<_sre.SRE_Match object; span=(0, 31), match='this is a number 56-89-89-458-1'>