原文链接
I did what I could, and I survived, there gotta be a reason.
我竭尽全力活了下来,那就不能白活。 —《深海浩劫》
使用正则表达式可以很方便的过滤、筛选我们需要的信息。
学习正则表达式是一个并不太愉快的过程,因为它确实有点像“火星语”。为方便查询,先列出快速的‘元字符’查询表(你可以先跳过这个表来学习):
表示法 | 表述 | 正则表达式式例 |
---|---|---|
literal | 匹配文本字符串的字面值 | foo |
re1 I re2 | 匹配正则表达式re1或re2 | foo l bar |
. | 匹配任何字符串(除了\n之外) | b.b |
^ | 匹配字符串起始部分 | ^Dear |
$ | 匹配字符串终止部分 | /bin/*sh$ |
* | 匹配0次或多次前面出现的正则表达式 | [A-Za-z0-9] |
+ | 匹配1次或多次前面出现的正则表达式 | [a-z]+.com |
? | 匹配0次或1次前面出现的正则表达式 | goo? |
{N} | 匹配N次前面出现的正则表达式 | [0-9]{3} |
{M,N} | 匹配M~N次前面出现的正则表达式 | [0-9]{5,9} |
[...] | 匹配来自字符集的任意单一字符 | [aeiou] |
[..x-y..] | 匹配x~y范围中的任意单一字符 | [0-9],[A-Za-z] |
[^...] | 不匹配此字符集中出现的任何一个字符,包括某一范围的字符 | [aeiou],[A-Za-z] |
(*l+l?l{})? | 用于匹配上面频繁出现符号的非贪婪版本(*、+、?、{}) | .*?[a-z] |
(...) | 匹配封闭的正则表达式,然后另存为子组 | ([0-9]{3})?,f(oo l u)bar |
特殊符号 | ||
\d | 匹配任何十进制数字,和[0-9]相同(\D与\d相反,不匹配任何非数值型的数字) | data\d.txt |
\w | 匹配任何字母数字字符,与[A-Za-z0-9_]相同(\W与之相反) | [A-Za-z_]\w+ |
\s | 匹配任何空格字符,与[\n\t\r\n\f]相同(与\S相反) | of\sthe |
\b | 匹配一个单词边界,也就是指单词和空格间的位置。例如,“er\b”可以匹配“never”中的“er”,但不能匹配“verb”中的“er”(与\B相反) | \bThe\b |
\B | 匹配非单词边界 | “er\B”能匹配“verb”中的“er”,但不能匹配“never”中的“er” |
\N | 匹配已保存的子组N(参加上面的(...)) | price:\16 |
\c | 逐字匹配任何特殊字符c | .,\,* |
\A(\Z) | 匹配字符串的起始(参见上面^$) | \ADear |
扩展表示法 | ||
(?iLmsux) | 在正则表达式中嵌入一个或者多个特殊“标记”参数(或者通过函数/方法) | (?x),(?im) |
(?:...) | 表示一个匹配不用保存的分组 | (?:\w+.)* |
(?P |
像一个仅有name标识而不是数字ID标识的正则分组匹配 | (?P) |
(?P=name) | 在同一字符串中匹配由(?P(?P=data) |
|
(?#...) | 表示注释,所有内容都被忽略 | (?#comment) |
(?=...) | 如果...跟在字符串后面才做匹配,非获取匹配;称作正向前视断言 | (?=.com) |
(?!...) | 如果...不跟在后面才做匹配操作,非获取匹配;称作负向前断言 | (?!.net) |
(?<=...) | 如果...出现在字符串前面才做匹配,称作正向后视断言 | (?<=800-) |
(? | 如果...不出现在字符串前面才做匹配,称作负向后断言 | (? |
(?(id/name)Y l N) | 如果分组提供的id或name(名称)存在,就返回正则表达式的条件匹配Y,如果不存在,就返回N;l N是可选项 | (?(1)y l x) |
只看上面的表格,还是不太会用,那么咱们来分开一个个的说说:
1.择一匹配符号“|”
表示从多个模式中选择一个,用于分割不同的正则表达式。例如:
at | home #可以匹配字符串at或home
2.任意字符匹配符号“.”
“.”号可以匹配除了换行符以为的任何字符(Python正则表达式有一个编译标记[S或DOTALL]能够使“.”匹配换行符),要匹配“.”号自身,必须使用反斜线转译符号“\.”。例如:
f.o #能够匹配f和o之间加上任意一个字符的样式,如:fao、f9o、f#o
.. #能够匹配任意两个字符
3.**匹配字符串开始“^”或结尾“”或\Z;
^From #任何以From开始的字符串
tcsh$ #任何以tcsh结尾的字符串
^subject:hi$ #任何由单独的字符串subject:hi构成的字符串
4.匹配单词边界:“\b”、“\B”
\b:匹配单词的边界(单词前或后),而不在乎单词中间的字符
\B:匹配单词中间的字符,而不在乎单词边界的字符
er\b #可以匹配“never”中的“er”,但不能匹配“verb”中的“er”,只关心后边
er\B #能匹配“verb”中的“er”,但不能匹配“never”中的“er”,只关心中间
\bthe #匹配任何以the开头的字符串
5.字符集“[ ]”
当想要匹配指定的某些字符的时候,使用字符集是很方便的。
注意:字符集只适用于单字符的情况。也就是说[ab]表示只从ab中选择一个
b[ae]t #匹配bat、或bet
[01][ab] #匹配0a、0b、1a、1b
6.字符集中的范围“-”和否定“[^]”
z.[0-9] #字母z后面跟着任何一个字符,然后跟着一个数字
[^aeiou] #一个非元音字符
[^\t\n] #不匹配制表符或\n
["-a] #在一个ASCII系统中,位于“"”和“a”之间的字符,即34-97之间的字符
7.特殊符号、+、?、{}*
*:匹配其左边的正则表达式出现零次或多次的情况。
+:匹配一次或多次出现的正则表达式。
?:匹配零次或一次出现的正则表达式。
{N}、{M,N}: 匹配前面的正则表达式N次或M~N次
[dn]ot? #字母d或n后面跟一个o,然后后面最多再跟一个t。例如:do、no、dot、not
0?[1-9] #一个1到9的数字,前面跟或不跟一个0
[0-9]{15,16} #匹配15或16个数字。例如信用卡号码
?[^>]+> #匹配全部有效的(和无效的)HTML标签
8.特殊字符
\d: 十进制数字
\w: 全部字母数字,相当于[A-Za-z0-9]
\s: 空格字符
这些字符的大些表示不匹配对应小写的内容。
\w+-\d+ #一个由字母数字组成的字符串和一串由连字符分隔的数字
[A-Za-z]\w* #第一个是字母,其余是字母或数字
\d{3}-\d{3}-\d{4} #美国电话号码格式,例如800-555-1212
\w+@\w+\.com #以[email protected]格式表示的简单电子邮件弟子
9.圆括号指定分组
有时候除了进行匹配操作外,我们还想要提取所匹配的子组,例如:\w+-\d+,这个正则表达式想要分别保存第一部分的字母和第二部分的数字,该怎么实现?我们可能这样做的原因是对于任何成功的匹配,我们想要看到匹配的字符串究竟是什么。如果为两个子模块都加上圆括号,例如(\w+)-(\d),然后就能够分别访问每一个匹配的子组。
\d+(\.\d*)? #匹配浮点数的字符串,如:“5”、“5.”、“5.009”等
10.扩展表示法
可以参考上面表格的讲解结合下面的例子就能懂了:
Windows(?=95|98|NT|2000) #能匹配“Windows2000”中的“Windows”,但不能匹配“Windows3.1”中的“Windows”
Windows(?!95|98|NT|2000) #能匹配“Windows3.1”中的“Windows”,但不能匹配“Windows2000”中的“Windows”
(?<=95|98|NT|2000)Windows #能匹配“2000Windows”中的“Windows”,但不能匹配“3.1Windows”中的“Windows”
(?
正则表达式和Python语言
Python语言中使用re模块的方法支持正则表达式。这里列出re模块常见的函数以方便查询(后面会介绍主要的函数使用方法)
函数 | 描述 |
---|---|
仅仅是re模块函数 | |
compile(pattern, flags = 0) | 使用任何可选的标记来编译正则表达式的模式,然后返回一个正则表达式对象 |
re模块函数和正则表达式对象的方法 | |
match(pattern, string, flags = 0) | 尝试使用带有可选的标记的正则表达式的模式来匹配字符串。如果匹配成功就返回匹配对象,如果失败就返回None |
search(pattern, string, flags = 0) | 使用可选标记搜索字符串中第一次出现的正则表达式模式。如果匹配成功就返回匹配对象,如果失败就返回None |
findall(pattern, string [, flags]) | 查找字符串中所有(非重复)出现的正则表达式模式,并返回一个匹配列表 |
finditer(pattern, string[, flags]) | 与findall()函数相同,但返回的不是一个列表,而是一个迭代器。对于每一次匹配,迭代器都返回一个匹配对象 |
split(pattern, string, max=0) | 根据正则表达式的模式分隔符,split函数将字符串分隔为列表,然后返回成功匹配的列表,分隔最多操作max次(默认分隔所有成功匹配的位置) |
sub(pattern, repl, string, count = 0) | 使用repl替换所有正则表达式的模式在字符串中出现的位置,除非定义count,否则就将替换所有出现的位置 |
purge() | 清除隐式编译的正则表达式模式 |
常用匹配对象方法 | |
group(num = 0) | 返回整个匹配对象,或者编号为num的特性子组 |
groups(default = None) | 返回一个包含所有匹配子组的元组(如果没有成功匹配,则返回一个空元组) |
groupdict(default = None) | 返回一个包含所有匹配的命名子组的字典,所有的子组名称作为字典的键(如果没有匹配成功,就返回一个空元组) |
常用模块属性(用于大多数正则表达式函数的标记) | |
re.I、re.IGNORECASE | 不区分大小写的匹配 |
re.L、re.LOCALE | 根据所使用的本地语言环境通过\w、\W、\b、\B、\s、\S实现匹配 |
re.M、re.MULTILINE | ^和$分别匹配目标字符串中行的起始和结尾,而不是严格匹配整个字符串本身的起始和结尾 |
re.S、re.DOTALL | "."(点号)通常匹配除了\n之外的所有单个字符,该标记表示"."能够匹配全部字符 |
re.X、re.VERBOSE | 通过反斜线转译,否则所有空格加上#(以及在该行中所有后续文字)都被忽略,除非在一个字符类中或者允许注释并提高可读性 |
下面将分开解释上面的部分函数:
1.使用match()和search()匹配字符串,使用group()查看结果
match() :从字符串开始的位置匹配,成功返回匹配的对象,失败返回None
search(): 扫描整个字符串来进行匹配,成功返回匹配的对象,失败返回None
例1:比较match() 和 search()的区别
import re
m = re.match('foo', 'seafood')
if m is not None: print("match-" + m.group())
m = re.search('foo', 'seafood')
if m is not None: print("search-" + m.group())
#结果是:search-foo
例2: match()函数从起始位开始匹配
import re
m = re.match('foo', 'foo')
if m is not None:
print("能匹配-" + m.group())
m = re.match('foo', 'bar')
if m is not None: print("不能匹配-" + m.group())
m = re.match('foo', 'food on the table')
if m is not None: print("从开始位置进行匹配-" + m.group())
#能匹配-foo
#从开始位置进行匹配-foo
例3: 匹配多个值(使用择一表达式"|")
import re
bt = 'bat|bet|bit'
m = re.match(bt, 'bat')
if m is not None:
print("1能匹配-" + m.group())
m = re.match(bt, 'blt')
if m is not None:
print("2能匹配-" + m.group())
m = re.match(bt, 'he bit me')
if m is not None:
print("3能匹配-" + m.group())
m = re.search(bt, 'he bit me')
if m is not None:
print("4能匹配-" + m.group())
#结果:
# 1能匹配-bat
# 4能匹配-bit
例4: 匹配任何单个字符
点号"."除了换行符\n和非字符,都能匹配
import re
bt = ".end"
m = re.match(bt, 'bend')
if m is not None:
print("bend能匹配-" + m.group())
m = re.match(bt, 'end')
if m is not None:
print("end能匹配-" + m.group())
m = re.match(bt, '\nend')
if m is not None:
print("\nend能匹配-" + m.group())
m = re.search(bt, 'the end.')
if m is not None:
print("the end.能匹配-" + m.group())
#结果:
# bend能匹配-bend
# the end.能匹配- end
例5: 匹配小数点
import re
bt = "3.14"
pi_bt = "3\.14" #表示字面量的点号 (dec.point)
m = re.match(bt, '3.14') #点号匹配
if m is not None:
print("3.14能匹配-" + m.group())
m = re.match(pi_bt, '3.14') #精确匹配
if m is not None:
print("精确匹配-" + m.group())
m = re.match(bt, '3014') #点号匹配0
if m is not None:
print("3014能匹配-" + m.group())
#结果:
# 3.14能匹配-3.14
# 精确匹配-3.14
# 3014能匹配-3014
例6: 使用字符集"[ ]"
import re
bt = "[cr][23][dp][o2]"
m = re.match(bt, 'c3po') #点号匹配
if m is not None:
print("c3po能匹配-" + m.group())
#结果:
# c3po能匹配-c3po
例7: 重复、特殊字符
正则表达式: \w+@\w+.com可以匹配类似[email protected]的邮箱地址,但是类似[email protected]的地址就不能匹配了。这时候我们可以使用* 操作符来表示该模式出现零次或者多次:\w+@(\w+.)*\w+.com
例8: 分组
group()可以访问每个独立的子组
groups()获取一个包含所有匹配子组的元组
>>> import re
>>> m = re.match('(\w\w\w)-(\d\d\d)', 'abc-123')
>>> m.group()
'abc-123'
>>> m.group(1)
'abc'
>>> m.group(2)
'123'
>>> m.groups()
('abc', '123')
>>> m = re.match('ab', 'ab')
>>> m.group()
'ab'
>>> m.groups()
()
例9: 匹配字符串起始和结尾
m = re.search('^the','the end.')
>>> m.group()
'the'
>>> m = re.search('^the','sthe end.')
>>> m.group()
Traceback (most recent call last):
File "", line 1, in
AttributeError: 'NoneType' object has no attribute 'group'
>>> m = re.search(r'\bthe','bite the dog')
>>> m.group()
'the'
>>> m = re.search(r'\bthe','bitethe dog')
>>> m.group()
Traceback (most recent call last):
File "", line 1, in
AttributeError: 'NoneType' object has no attribute 'group'
>>> m = re.search(r'\Bthe','bitethe dog')
>>> m.group()
'the'
2.使用findall()、finditer()查找每一次出现的位置
final() 以列表的形式返回所有能匹配的结果
>>> import re
>>> re.findall('car', 'car sscare')
['car', 'car']
finaliter()返回一个顺序访问每一个匹配结果(Match对象)的迭代器
>>> re.finditer(r'(th\w+) and (th\w+)',s, re.I).next().group(1)
'This'
>>> re.finditer(r'(th\w+) and (th\w+)',s, re.I).next().group(2)
'That'
3.使用sub()和subn()搜索和替换
两个函数都可以实现搜索和替换功能,将某字符串中所有匹配正则表达式的部分进行某种形式的替换。不同点是subn()还返回一个表示替换了多少次的总数,和返回结果一起以元组的形式返回。
>>> re.sub('[ae]','X','abcdef')
'XbcdXf'
>>> re.subn('[ae]','X','abcdef')
('XbcdXf', 2)
进行替换的时候,还可以指定替换的顺序,原理是使用匹配对象的group()方法除了能够获取匹配分组编号外,还可以使用\N,其中N表示要替换字符串中的分组的编号,通过编号就能指定替换的顺序。
例如:将美式日期MM/DD/YY{,YY}格式转换成DD/MM/YY{,YY}格式
>>> re.sub(r'(\d{1,2})/(\d{1,2})/(\d{2}|\d{4})',r'\2/\1/\3','2/20/91')
'20/2/91'
>>> re.sub(r'(\d{1,2})/(\d{1,2})/(\d{2}|\d{4})',r'\2/\1/\3','2/20/1991')
'20/2/1991'
4.在限定模式上使用split()分隔字符串
re模块的split()可以基于正则表达式的模式分隔字符串。但是当处理的不是特殊符号匹配多重模式的正则表达式时,re.split()和str.split()的工作方式相同,如下所示:
>>> re.split(':', 'str1:str2')
['str1', 'str2']
>>> 'str1:str2'.split(':')
['str1', 'str2']
但当处理复杂的分隔时,就需要比普通字符串分隔更强大的处理方式,例如下面匹配复杂情况:
>>> DATA = ('Mountation View, CA 94040', 'sunnyvale, CA', 'Los Altos, 94023', 'Palo Alto CA','Cupertino 95014')
>>> for datum in DATA: print(re.split(', |(?= (?:\d{5}|[A-Z]{2})) ',datum))
...
['Mountation View', 'CA', '94040']
['sunnyvale', 'CA']
['Los Altos', '94023']
['Palo Alto', 'CA']
['Cupertino', '95014']
上述的正则表达式:当一个空格紧跟在5个数字或2个字母后面时就用split语句分隔。当遇到“,”也用split函数分隔。
5.扩展符号
通过使用(?iLmsux)系列选项,可以直接在正则表达式里面指定一个活着多个标记。以下是使用re.I/IGNORECASE的示例,第二个是使用re.M/MULTILINE实现多行混合。
>>> re.findall(r'(?i)yes','yes? Yes. YES!!!')
['yes', 'Yes', 'YES']
>>> re.findall(r'(?i)th\w+','The quickest way is through this tunnel.')
['The', 'through', 'this']
>>> re.findall(r'(?im)(^th[\w ]+)', """
... This is the first,
... another line,
... that line,it's the best
... """)
['This is the first', 'that line']
通过使用“多行”,能够在目标字符串中实现跨行搜索,而不必将整个字符串视为单个实体。
下一个例子用来演示re.S/DOTALL,该标记表示点号(.)能够用来表示\n符号。
>>> re.findall(r'th.+',"""
... The first line
... the second line
... the third line
... """)
['the second line', 'the third line']
>>> re.findall(r'(?s)th.+',"""
... The first line
... the second line
... the third line
... """)
['the second line\nthe third line\n']
re.X/VERBOSE标记允许用户通过抑制在正则表达式中使用空白符来创建更易读的正则表达式。
>>> re.search(r'''(?x)
... \((\d{3})\) #区号
... [ ] #空白符
... (\d{3}) #前缀
... - #横线
... (\d{4}) #终点数字
... ''','(800) 555-1212').groups()
('800', '555', '1212')
(?:...)符号可以对部分正则表达式进行分组,但是不会保存该分组用于后续的检索或应用。
>>> re.findall(r'http://(?:\w+\.)*(\w+\.com)',
... 'http://google.com http://www.google.com http://code.google.com')
['google.com', 'google.com', 'google.com']
>>> re.search(r'\((?P\d{3})\) (?P\d{3})-(?:\d{4})',
... '(800) 555-1212').groupdict()
{'areacode': '800', 'prefix': '555'}
可以同时使用(?P
>>> re.sub(r'\((?P\d{3})\) (?P\d{3})-(?:\d{4})',
... '(\g) \g-xxxx', '(800) 555-1212')
'(800) 555-xxxx'
使用后者,可以在同一个正则表达式中重用模式。例如,验证一些电话号码的规范化。
bool(re.match(r'\((?P\d{3})\) (?P\d{3})-(?P\d{4}) (?P=areacode)-(?P=prefix)-(?P=number) 1(?P=areacode)(?P=prefix)(?P=number)', '(800) 555-1212 800-555-1212 18005551212'))
True
使用(?x)使代码更易读:
>>> bool(re.match(r'''(?x)
... \((?P\d{3})\)[ ](?P\d{3})-(?P\d{4})
... [ ]
... (?P=areacode)-(?P=prefix)-(?P=number)
... [ ]
... 1(?P=areacode)(?P=prefix)(?P=number)
... ''','(800) 555-1212 800-555-1212 18005551212'))
True
可以使用(?=...)和(?!...)符号在目标字符串中实现一个前视匹配:
(?=...)字符串后面跟着...才适配
>>> re.findall(r'\w+(?= van Rossum)',
... '''
... Guido van Rossum
... Tim Peters
... Alex Martelli
... Just van Rossum
... Raymond Hettinger
... ''')
['Guido', 'Just']
(?!...)字符串后面不跟着...才适配:
>>> re.findall(r'(?m)^\s+(?!noreply|postmaster)(\w+)',
... '''
... [email protected]
... [email protected]
... [email protected]
... [email protected]
... [email protected]
... ''')
['sales', 'eng', 'admin']
比较re.findall()和re.finditer()
>>> ['%s@awcom' % e.group(1) for e in re.finditer(r'(?m)^\s+(?!noreply|postmaster)(\w+)',
... '''
... [email protected]
... [email protected]
... [email protected]
... [email protected]
... [email protected]
... ''')]
['admin@awcom', 'eng@awcom', 'sales@awcom']
条件正则表达式匹配,假定拥有一个特殊字符,它仅仅包含字母x和y,两个字母必须由一个跟着另外一个,不能同时拥有相同的两个字母:
>>> bool(re.search(r'(?:(x)|y)(?(1)y|x)', 'xy'))
True
>>> bool(re.search(r'(?:(x)|y)(?(1)y|x)', 'xx'))
False
实例
在UNIX系统中,who命令会展示登录的用户信息。例如:
➜ ~ who
sl console Nov 21 08:59
sl ttys000 Nov 21 09:09
sl ttys001 Nov 21 10:30
➜ ~
如果想按照空格(多个,数量不确定)分隔的话,可以使用\s\s+,下面创建一个程序,将保存在文件whodata.txt中的数据读出来:
先将who的数据保存在whodata.txt文件中:
➜ ~ who > /Users/sl/Desktop/whodata.txt
然后执行下面的程序:
import re
f = open('whodata.txt','r')
for eachLine in f:
print(re.split(r'\s\s+', eachLine))
f.close()
执行结果:
['sl', 'console', 'Nov 21 08:59', '']
['sl', 'ttys000', 'Nov 21 09:09', '']
['sl', 'ttys001', 'Nov 21 10:30', '']
优化上面的程序:
上面的程序,who命令是在脚本外部执行的,每次手动重复做这件事让人很厌倦,我们可以通过调用os.popen()命令(现在已经被subprocess模块替代)将这个命令的执行在脚本内部实现。另外我们使用str.rstrip()去除尾部的\n,程序如下:
import re
import os
f = os.popen('who', 'r')
for eachLine in f:
print(re.split(r'\s\s+|\t', eachLine.rstrip()))
f.close()
#结果:
['sl', 'console', 'Nov 21 08:59']
['sl', 'ttys000', 'Nov 21 09:09']
['sl', 'ttys001', 'Nov 21 10:30']
还可以使用with语句,可以使上下文管理对象变得更简易:
import re
import os
with os.popen('who', 'r') as f:
for eachLine in f:
print(re.split(r'\s\s+|\t', eachLine.rstrip()))
如果要适配python2和python3的话,可以避免使用print(),而使用两个版本中都有的函数distutils.log.warn(),并将其转换成printf名来使用。
import re
import os
from distutils.log import warn as printf
with os.popen('who', 'r') as f:
for eachLine in f:
printf(re.split(r'\s\s+|\t', eachLine.rstrip()))
生成随机数的例子,用于希望练习从中匹配、搜索正则表达式使用:
from random import randrange, choice
from string import ascii_lowercase as lc
from sys import maxsize
from time import ctime
tlds = ('com', 'edo', 'net', 'org', 'gov')
for i in range(randrange(5, 11)):
dtint = randrange(maxsize) / 3000000000
dtstr = ctime(dtint)
llen = randrange(4, 8)
login = ''.join(choice(lc) for j in range(llen))
dlen = randrange(llen, 13)
dom = ''.join(choice(lc) for j in range(dlen))
print('%s::%s@%s.%s::%d-%d-%d' % (dtstr, login, dom, choice(tlds), dtint, llen, dlen))
#随机产生的结果
Mon Jun 24 08:07:21 2024::[email protected]::1719187641-7-8
Sun May 21 12:23:33 2062::[email protected]::2915411013-5-12
Thu Sep 2 18:27:12 1999::[email protected]::936268032-7-9
Mon Jul 30 16:45:03 2007::[email protected]::1185785103-5-8
Thu Jul 8 01:50:54 1971::[email protected]::47757054-4-5
Thu Sep 1 03:02:30 2005::[email protected]::1125514950-4-8
Sun Nov 27 01:23:35 2011::[email protected]::1322328215-4-10
Thu Aug 1 18:36:36 2024::[email protected]::1722508596-6-7
想深入学习正则表达式高级用法推荐