python-正则表达式
正则表达式(RE)是一些由字符和特殊字符组成的字符串,它们描述了这些字符和字符的某种重复方式,因此能按照某种模式匹配一个有相似特征的字符串的集合,因此能按某种模式匹配一系类有相似特征的字符串。
Python通过标准库re模块支持正则表达式。
说明一下术语“匹配”和“搜索”的区别:
Python专门术语中,有两种主要方法完成匹配:搜索和匹配。搜索:即在字符串任意部分中搜索匹配的模式。匹配:判断一个字符能否从开始处全部或部分的匹配某个模式。搜索通过search()函数或方法实现,匹配调用match()函数或方法实现。我们说模式的时候,使用的是术语“匹配”。
1.1 正则表达式使用的特殊符号和字符
记号 |
说明 |
正则表达式样例 |
literal |
匹配字符串的值 |
foo |
re1/re2 |
匹配正则表达式re1或re2 |
foo|bar |
. |
匹配任意字符(换行符除外) |
b.b |
^ |
匹配字符串的开始 |
^Dear |
$ |
匹配字符串的结尾 |
/bin/*sh$ |
* |
匹配前面出现的正则表达式零次或多次 |
[A-Za-z0-9]* |
+ |
匹配前面出现的正则表达式出现一次或多次 |
[a-z]+\.com |
? |
匹配前面出现的正则表达式零次或一次 |
goo? |
{N} |
匹配前面出现的正则表达式N |
[0-9]{3} |
{M,N} |
匹配重复出现M次到N次的正则表达式 |
[0-9]{5,9} |
[…] |
匹配字符组里出现的任意一个字符 |
[aeiou] |
[.,x-y,.] |
匹配从字符x到字符中的任意一个字符 |
[0-9],[A-Za-z] |
[^…] |
不匹配此字符集中出现的任意一个字符,包括某一范围的字符(如果在此字符集中出现) |
[^aeiou],[^A-Za-z0-9] |
(*|+|?|{})? |
用于上面出现的任何“非贪婪”。版本重复匹配次数符号 |
.*?[a-z] |
(…) |
匹配封闭括号中正则表达式(RE),并保存为子组 |
([0-9]{3})?,f(oo|u)bar |
特殊字符 |
||
\d |
匹配任何数字,和[0-9]一样,(\D是\d的反义,任何非数符字) |
data\d+.txt |
\w |
匹配任何数字字母字符,和[A-Za-z0-9_]相同,(\W是\w的反义) |
[A-Za-z_]\w+ |
\s |
匹配任何空白符,和[\n\t\r\v\f]相同。(\S是\s的反义) |
of\sthe |
\b |
匹配单词边界。(\B是\b的反义) |
\bThe\b |
\nn |
匹配已保存的子组 |
prince:\16 |
\c |
逐一匹配特殊字符c(即:取消它的特殊含义,按字面匹配) |
\.,\\, \* |
\A(\Z) |
匹配字符串的起始(结束) |
\ADear |
(1)用管道符号(|)匹配多个正则表达式模式
管道符号(|),表示一个或操作,它的意思是选择被管道符号分割的多个不同的正则表达式中的一个。
(2)匹配任意一个单个的字符(.)
点符号或句点(.)符号匹配除换行符(NEWLINE)外的任意一个单个字符(Python的正则表达式有一个编译标识[S or DOTALL]),该标识能去掉这一限制,使(.) 在匹配时包括换行符。无论是字母、数字、不包括“\n”的空白符、可打印的字符、还是非打印字符,或是一个符号,点(.)都可以匹配它们。
(3)从字符串的开头或结尾或单词边界开始匹配(^ $ \b \B)
^或\A:用于从字符串的开头开始匹配一个模式。
如果想匹配这两个字符中的任何一个(或全部),就必须用反斜线进行转义。
\b或\B:用来匹配单词边界,\b匹配的模式是一个单词边界(一定在一个单词的开头,不论这个单词的前面是有字符,还是没有字符),\B只匹配出现在一个单词中间的模式(不在单词边界上的字符)。
(4)创建字符类([])
使用方括号的正则表达式会匹配方括号里的任意一个字符。
(5)指定范围(-)和否定(^)
方括号里的一对字符中间的连字符(-)用来表示一个字符的范围。例如:z.[0-9]
如果左方括号后第一个字符是上箭头符号(^),就表示不匹配指定字符集里的任意字符。例如:[^aeiou]
(6)使用闭包操作符(*,+,?,{})实现多次出现/重复匹配
*:匹配它左边那个正则表达式出现零次或零次以上的情况。
+:匹配它左边那个正则表达式至少出现一次的情况。
?:匹配它左边那个正则表达式出现零次或一次的情况。
{N}:匹配N次出现。
{M,N}:匹配M次到N次出现。
(7)特殊字符表示
\d:0-9范围内的十进制数。
\w:整个字符数字的字符集,相当于“A-Za-z0-9_”。
\s:表示空白字符。
这些特殊字符的大写形式表示不匹配。
(8)用圆括号(())组建组
一对圆括号(())和正则表达式使用实现的功能有:1.对正则表达式进行分组;2.匹配子组。
使用圆括号匹配的子串会被保存到一个子组。这些字组可以在同一次匹配或搜索中被重复调用。
1.2 正则表达式和Python语言
1.2.1 re模块:核心函数和方法
常用的函数和方法:
函数/方法 |
描述 |
模块的函数 |
|
compile(pattern, flags=0) |
对正则表达式模式pattern进行编译,flags是可选标识符,并返回一个regex对象(已编译的正则表达式对象)。 |
re模块的函数和regex对象的方法 |
|
match(pattern, string, flags=0) |
尝试用正则表达式pattern匹配字符串string,flags是可选标识符,如果匹配成功,则返回一个匹配对象,否则返回None |
search(pattern, string, flags=0) |
在字符串string中搜索正则表达式模式pattern的第一次出现,flags是可选标识符,如果匹配成功,则返回一个匹配对象,否则返回None。 |
findall(pattern, string, [,flags]) |
在字符串string中搜索正则表达式模式pattern的所有(非重复)出现,返回一个匹配对象的列表。 |
finditer(pattern, string [,flags]) |
和findall()相同,但返回的不是列表而是迭代器;对于每个匹配,该迭代器返回一个匹配对象。 |
split(pattern, string, max=0) |
根据正则表达式pattern中的分隔符把字符string分割为一个列表,返回成功匹配的列表,最多分割max次(默认是分割所有匹配的地方)。 |
sub(pattern, repl, string, max=0) |
把字符串中所有匹配正则表达式pattern的地方替换成字符串repl,如果max的值没有给出,则对所有匹配的地方进行替换。 |
匹配对象的方法 |
|
group(num=0) |
返回全部匹配对象(或指定编号是num的子组) |
groups() |
返回一个包含全部匹配的子组的元组(如果没有成功匹配,就返回一个空元祖)。 |
RE编译(何时应该使用compile函数?)
在模式匹配之前,正则表达式模式必须先被编译成regex对象,由于正则表达式在执行过程中被多次用于比较,建议先对它做预编译。re.compile()函数提供了此功能。
1.2.2 匹配对象和group()、groups()方法
在处理正则表达式中,出regex对象外,还有一种对象类型-匹配对象,通过match()函数和search()函数被成功调用后返回。匹配对象有两个主要方法:group()和groups()。
group():或返回所有匹配对象或根据要求返回某个特定子组。
groups():返回一个包含唯一或所有子组的元组。
1.2.3 用match()匹配字符串
match():从字符串开头开始对模式进行匹配,如果匹配成功,返回一个匹配对象,如果失败,返回None。匹配对象的group()方法可以用来显示那个成功的匹配。
1 m = re.match('foo', 'foo') 2 >>> if m is not None: 3 ... m.group() 4 ... 5 'foo' 6 >>> m 7 <_sre.SRE_Match object at 0x7f4e059a9648> 8 >>> m = re.match('foo', 'food on table') 9 >>> m.group() 10 'foo'
1.2.4 search()在一个字符串中查找一个模式
search():检查参数字符串任意位置的地方给定正则表达式的匹配情况,如果搜索到成功的匹配,会返回一个匹配对象,否则返回None。
1 >>> m = re.match('foo', 'seafood') 2 >>> m.group() 3 Traceback (most recent call last): 4 File "<stdin>", line 1, in <module> 5 AttributeError: 'NoneType' object has no attribute 'group' 6 >>> m = re.search('foo', 'seafood').group() 7 >>> m 8 'foo'
1.2.5 匹配多个字符串(|)
1 >>> bt = 'bat|bet|bit' 2 >>> m = re.match(bt, 'bat') 3 >>> m 4 <_sre.SRE_Match object at 0x7f4e059a9ac0> 5 >>> m.group() 6 'bat' 7 >>> m = re.match(bt, 'bit') 8 >>> m.group() 9 'bit' 10 >>> m = re.match(bt, 'bet') 11 >>> m.group() 12 'bet'
1.2.6 匹配任意单个字符(.)
句点是不能匹配换行符或非字符(即空字符)的。
1 >>> m = re.match(anyend, 'bend') 2 >>> if m is not None: m.group() 3 ... 4 'bend' 5 >>> m = re.match(anyend, '\nend') 6 >>> if m is not None: m.group() 7 ... 8 >>> m = re.match(anyend, 'end') 9 >>> if m is not None: m.group() 10 ... 11 >>> m = re.match(anyend, 'the end') 12 >>> 13 >>> if m is not None: m.group() 14 ... 15 >>> 16 >>> patt314 = '3.14' 17 >>> pi_patt = '3\.14' 18 >>> m = re.match(pi_patt, '3.14') 19 >>> if m is not None:m.group() 20 ... 21 '3.14' 22 >>> m = re.match(patt314, '3014') 23 >>> if m is not None:m.group() 24 ... 25 '3014' 26 >>> m = re.match(patt314, '3.14') 27 >>> if m is not None:m.group() 28 ... 29 '3.14' 30 >>>
1.2.7 创建字符合集([])
1 >>> m = re.match('[cr][23][dp][o2]', 'c3po') 2 >>> if m is not None:m.group() 3 ... 4 'c3po' 5 >>> m = re.match('[cr][23][dp][o2]', 'c2po') 6 >>> 7 >>> if m is not None:m.group() 8 ... 9 'c2po' 10 >>> m = re.match('r2d2|c3po', 'c2po') 11 >>> 12 >>> if m is not None:m.group() 13 ... 14 >>> m = re.match('r2d2|c3po', 'r2po') 15 >>> if m is not None:m.group() 16 ... 17 >>> m = re.match('r2d2|c3po', 'r2d2') 18 >>> 19 >>> if m is not None:m.group() 20 ... 21 'r2d2' 22 >>>
1.2.8 重复、特殊字符和子组
例如下面的例子,表达式容许“.com”前面有一个或两个名字:
1 >>> patt = '\w+@(\w+\.)?\w+\.com' 2 >>> re.match(patt, 'nobody@xxx.com').group() 3 'nobody@xxx.com'
接着修改,允许任意数量的子域存在。
1 >>> patt = '\w+@(\w+\.)*\w+\.com' 2 >>> re.match(patt, 'nobody@xxx.yyy.zzz.com').group() 3 'nobody@xxx.yyy.zzz.com'
分别提取包含字母或数字的部分或仅含有数字的部分。
1 >>> m = re.match('(\w\w\w)-(\d\d\d)', 'abc-123') 2 >>> m.group() 3 'abc-123' 4 >>> m.group(1) 5 'abc' 6 >>> m.group(2) 7 '123' 8 >>> m.groups() 9 ('abc', '123')
group()通常用来显示所有匹配部分,也可以用来获取个别匹配的子组,groups()方法获得一个包含所有匹配子组的元组。
1 >>> m = re.match('ab', 'ab') 2 >>> m.group() 3 'ab' 4 >>> m.groups() 5 () 6 >>> m = re.match('(ab)', 'ab') 7 >>> m.group() 8 'ab' 9 >>> m.group(1) 10 'ab' 11 >>> m.groups() 12 ('ab',) 13 >>> m = re.match('(a)(b)', 'ab') 14 >>> m.group() 15 'ab' 16 >>> m.group(1) 17 'a' 18 >>> m.group(2) 19 'b' 20 >>> m.groups() 21 ('a', 'b') 22 >>> m = re.match('(a(b))', 'ab') 23 >>> m.group() 24 'ab' 25 >>> m.group(1) 26 'ab' 27 >>> m.group(2) 28 'b' 29 >>> m.groups() 30 ('ab', 'b')
1.2.9 从字符串的开头或结尾匹配及在单词边界上的匹配
这些是锚点性正则表达式,这些锚点性正则表达式主要用于搜素而不是匹配,因为match()总是从字符串的开头进行匹配的。
1 >>> m = re.search('^The', 'The end') #在开头 2 >>> if m is not None:m.group() 3 ... 4 'The' 5 >>> m = re.search('^The', 'end. The') 6 >>> if m is not None:m.group() 7 ... 8 >>> m = re.search(r'\bthe', 'bite the dog') #在词边界 9 >>> if m is not None:m.group() 10 ... 11 'the' 12 >>> m = re.search(r'\bthe', 'bitethe dog') 13 >>> if m is not None:m.group() 14 ... 15 >>> m = re.search(r'\Bthe', 'bitethe dog') #不在词边界 16 >>> if m is not None:m.group() 17 ... 18 'the'
1.2.10 用findall()找到每个出现的匹配部分
findall():用于非重叠的搜索某字符串中一个正则表达式模式出现的情况。返回的总是一个列表,如果没有匹配,返回空列表;如果成功找到匹配部分,则返回所有匹配部分的列表(按从左到右出现的顺序排列)。
1 >>> re.findall('car', 'car') 2 ['car'] 3 >>> re.findall('car', 'scary') 4 ['car'] 5 >>> re.findall('car', 'carry the barcardi to the car') 6 ['car', 'car', 'car']
1.2.11 用sub()(和subn())进行搜索和替换
两者几乎一样,都是将某字符串中所有匹配正则表达式模式的部分进行替换。用来替换的部分通常是一个字符串,但也可能是一个函数,该函数返回一个用来替换的字符串。
subn()还返回一个表示替换次数的数字,替换后的字符串和表示替换次数的数字作为一个元组的元素返回。
1 >>> re.sub('X', 'Mr.Smith', 'attn: X\n\nDear X,\n') 2 'attn: Mr.Smith\n\nDear Mr.Smith,\n' 3 >>> re.subn('X', 'Mr.Smith', 'attn: X\n\nDear X,\n') 4 ('attn: Mr.Smith\n\nDear Mr.Smith,\n', 2) 5 >>> print re.sub('X', 'Mr.Smith', 'attn: X\n\nDear X,\n') 6 attn: Mr.Smith 7 8 Dear Mr.Smith, 9 10 >>> re.sub('[ae]', 'X', '[abcdef]') 11 '[XbcdXf]' 12 >>> re.subn('[ae]', 'X', '[abcdef]') 13 ('[XbcdXf]', 2) 14 >>>
1.2.12 用split()分隔(分隔模式)
根据固定的字符串分隔。
1 >>> re.split(':', 'str1:str2:str3') 2 ['str1', 'str2', 'str3']
例子:Linux下who命令输出结果进行分隔
#!/usr/bin/env python from os import popen from re import split f = popen('who', 'r') for eachline in f.readlines(): print split('\s\s+|\t', eachline.strip()) f.close() 输出结果: [root@localhost tmp]# python rewho.py ['grace', 'tty1', '2020-01-26 14:18 (:0)'] ['grace', 'pts/0', '2020-01-26 14:18 (:0.0)'] ['root', 'pts/1', '2020-01-26 14:19 (192.168.230.1)']
核心笔记:python原始字符串的用法
原生字符串的产生是由于正则表达式的存在,由于ASCLL字符和正则表达式特殊字符间产生的冲突。例如“\b”在ASCLL字符中代表退格符,但同时也是一个正则表达式的特殊字符,代表“匹配一个单词边界”。为了让RE编辑器把两个字符‘\b’当成想要表达的字符串,可以是用r’\b’。
1 >>> m = re.match('\bblow', 'blow') 2 >>> if m is not None:m.group() 3 ... 4 >>> m = re.match('\\bblow', 'blow') 5 >>> 6 >>> if m is not None:m.group() 7 ... 8 'blow' 9 >>> m = re.match(r'\bblow', 'blow') 10 >>> if m is not None:m.group() 11 ... 12 'blow'
1.3 正则表达式示例
1.3.1 正则表达式示例
下面展示使用正则表达式处理字符串的不同办法。第一步:拿出一段代码用来生成随机数据,生成的数据用于以后操作。
1 from random import randint, choice 2 from string import lowercase 3 from sys import maxint 4 from time import ctime 5 6 doms = ['com', 'edu', 'net', 'org', 'gov'] 7 8 for i in range(randint(5, 10)): 9 dtint = randint(0, maxint-1) 10 dtstr = ctime(dtint) 11 shorter = randint(4, 7) 12 em = '' 13 for j in range(shorter): 14 em += choice(lowercase) 15 16 longer = randint(shorter, 12) 17 dn = '' 18 for j in range(longer): 19 dn += choice(lowercase) 20 21 print '%s::%s@%s.%s::%d-%d-%d' % (dtstr, em, dn, choice(doms), dtint, shorter, longer)
接下来使用生成的字符串来进行测试:
测试1:提取时间戳中的有关星期的数据字段
1 >>> data = 'Fri Sep 7 08:10:38 2035::vjxod@dgolkl.edu::2072736638-5-6' 2 >>> patt = '^(Mon|Tue|Wed|Thu|Fri|Sat|Sun)' 3 >>> m = re.match(patt, data) 4 >>> m.group() 5 'Fri' 6 >>> m.group(1) 7 'Fri' 8 >>> m.groups() 9 ('Fri',) 10 >>>
测试2:测试1例子的宽松表示
1 >>> data 2 'Fri Sep 7 08:10:38 2035::vjxod@dgolkl.edu::2072736638-5-6' 3 >>> patt = '^(\w{3})' 4 >>> m = re.match(patt, data) 5 >>> m.group() 6 'Fri' 7 >>> m.group(1) 8 'Fri' 9 >>>
1.3.2 搜索与匹配的比较,“贪婪”匹配
例如我们要搜索三个连字符号(-)分隔的整型集。
1 >>> data 2 'Fri Sep 7 08:10:38 2035::vjxod@dgolkl.edu::2072736638-5-6' 3 >>> patt = '\d+-\d+-\d' 4 >>> re.search(patt, data).group() 5 '2072736638-5-6' 6 >>> patt = '.+\d+-\d+-\d+' 7 >>> re.match(patt, data).group() 8 'Fri Sep 7 08:10:38 2035::vjxod@dgolkl.edu::2072736638-5-6' 9 >>> patt = '.+(\d+-\d+-\d+)' 10 >>> re.match(patt, data).group() 11 'Fri Sep 7 08:10:38 2035::vjxod@dgolkl.edu::2072736638-5-6' 12 >>> re.match(patt, data).group(1) 13 '8-5-6'
出现上面的原因是:正则表达式本身默认是贪心匹配的。也就是说,如果正则表达式中使用到通配字,那它在按照从左到右的顺序求值时,会尽量“抓取”满足匹配的最长字符串。在上面的例子中,“.+”会从字符串的开始处抓取满足模式的最长字符,其中包含我们想得到的第一个整型字符起始到这个第一位数字“8”之间的所有字符。
一个解决办法是用“非贪婪”操作符“?”。这个操作符可以用在“*”、“+”或“?”的后面。它的作用是要求正则表达式引擎匹配的字符越少越好。
1 >>> patt = '.+?(\d+-\d+-\d+)' 2 >>> re.match(patt, data).group(1) 3 '2072736638-5-6' 4 >>>
示例3:假设我们只想抽取三个整型字段里中间的那个整型部分
1 >>> data = 'Fri Sep 7 08:10:38 2035::vjxod@dgolkl.edu::2072736638-5-6' 2 >>> patt = '-(\d+)-' 3 >>> m = re.search(patt, data) 4 >>> m.group() 5 '-5-' 6 >>> m.group(1) 7 '5' 8 >>>