一、常见正则表达式符号和特殊字符
表达式 | 描述 | 正则表达式示例 |
符号 | ||
literal | 匹配文本字符串的字面值literal | foo |
rel1|rel2 | 匹配正则表达式rel1或rel2 | foo|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-z0-9] |
(*|+|?|{})? | 用于匹配上面频繁出现/重复出现符号的非贪婪版本(*、+、?、{}) | .*?[a-z] |
(...) | 匹配封闭的正则表达式,然后另存为子组 | ([0-9]{3})?,f(oo|u)bar |
特殊字符 | ||
\d | 匹配任何十进制数字,与[0-9]一致(\D与\d相反,不匹配任何非数值型的数字) | data\d+.txt |
\w | 匹配任何字母数字字符,与[A-Za-z0-9]相同(\W与之相反) | [A-Za-z_]\w+ |
\s | 匹配任何空格字符,与[\n\t\r\v\f]相同(\S与之相反) | of\sthe |
\b | 匹配任何单词边界(\B与之相反) | |
\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|N) | 如果分组所提供的id或name存在,就返回正则表达式的条件匹配Y,如果不存在,就返回N;|N是可选项 | (?(1)y|x) |
1.1 使用择一匹配符号匹配多个正则表达式
正则表达式模式 | 匹配的字符串 |
at|home | at、home |
bat|bet|bit | bat、bet、bit |
| 表示 从多个模式中选择其一 |
1.2 匹配任意单个字符
正则表达式模式 | 匹配的字符串 |
f.o | 匹配在字母“f”和“o”之间的任意一个字符:例如f#o、f9o |
.. | 任意俩个字符 |
.end | 匹配在字符串end之前的任意一个字符 |
.\. | 匹配句点符号本身 |
句号符号能匹配换行符\n以外的任何字符,但是Python正则表达式有一个编译标记[S或者DOTALL]能够推翻这个限制,使句号能够有匹配换行符 |
1.3 从字符串起始或者结尾或者单词边界匹配
正则表达式模式 | 匹配的字符串 |
^From | 任何以From作为起始的字符串 |
/bin/tcsh$ | 任何以/bin/tcsh作为结尾的字符串 |
^Subject:hi$ | 任何由单独的字符串Subject:hi构成的字符串 |
.*\$$ | 匹配任何以美元符号结尾的字符串 |
如果要匹配字符串的开始位置,就须使用脱字符^或字符\A,后者主要用于没有脱字符的键盘。美元符号$或者\Z将用于匹配字符串的末尾位置 |
正则表达式模式 | 匹配的字符串 |
the | 任何包含the的字符串 |
\bthe | 任何以the开始的字符串 |
\bthe\b | 仅仅匹配单词the |
\Bthe | 任何包含但并不以the作为起始的字符串 |
\b将用于匹配一个单词的边界,意味着如果一个模式必须位于单词的起始部分,就不管单词前面(单词位于字符串中间)是否由任何字符(单词位于行首)。同样,\B将匹配出现一个单词中间的模式(不是单词边界) |
1.4 创建字符集
正则表达式模式 | 匹配的字符串 |
b[aeiu]t | bat、bet、bit、but |
[cr][23][dp][o2] | 一个包含四个字符串,第一个字符使“c”或“r”,然后是“2”或“3”,后面是“d”或“p”,最后是“o”或“2”,例如:c2do、r3p2 |
如果要匹配一个字母的字符串,就可以使用正则表达式[ab]或a|b |
1.5 限定范围和否定
正则表达式模式 | 匹配的字符串 |
z.[0-9] | 字母“z”后面跟着任何一个字符,然后跟着一个数字 |
[r-u][env-y][us] | 字母“r”、“s”、“t”或者“u”后面跟着“e”、“n”、“v”、“w”、“x”或者“y”,然后跟着“u”或者“s” |
[^aeiou] | 一个非元音字符 |
[^\t\n] | 不匹配制表符或者\n |
[“-a] | 在一个ASCII系统中,所有字符都位于“”和“a”之间,即34~97之间 |
如果脱字符^紧跟在左方括号后面,这个符号就表示不匹配给定字符集中的任何一个字符 |
1.6 使用闭包操作符实现存在性和频数匹配
正则表达式模式 | 匹配的字符串 |
[dn]ot? | 字母“d”或者“n”,后面跟着一个“o”,然后是最多一个“t”,例如:do、no、dot、not |
0?[1-9] | 任何数值数字,它可能前置一个“0”,例如,匹配一系列数(表示从1~9数值),不管是一个还是俩个数字 |
[0~9]{15,16} | 匹配15或者16个数字(例如信用卡号码) |
]+> | 匹配全部有效的(和无效的)HTML标签 |
[KQRBNP][a-h][1-8]-[a-h][1-8] | 在“长代数”标记法中,表示国际象棋合法的棋盘移动(仅移动,不包括吃子和将军)。即“K”、“Q”、“R”、“B”、“N”、“P”等字母后面加上“a1”~“h8”之间的棋盘坐标。前面的坐标表示从哪里开始走棋,后面的坐标代表走到那个位置(棋格)上 |
星号*将匹配其左边的正则表达式出现零次或者多次的情况(在计算机编程语言和编译原理中,该操作称为Kleene闭包);加号+将匹配一次或多次出现的正则表达式(也叫做正闭包操作符);问号?将匹配零次或者一次出现的正则表达式。举例:
|
1.7 表示字符集的特殊字符
正则表达式模式 | 匹配的字符串 |
\w+-\d+ | 一个由字母数字组成的字符串和一串由一个连字符分隔的数字 |
[A-Za-z]\w* | 第一个字符是字母;其余字符(如果存在)可以是字母或者数字(几乎等价于Python中的有效标识符) |
\d{3}-\d{3}-\d{4} | 美国电话号码的格式,前面是区号前缀,例如800-555-1212 |
\w+@\w+\.com | 以XXX@YYY.com格式表示的简单电子邮件地址 |
与使用“0-9”这个范围表示十进制数相比,可以简单地使用d表示匹配任何十进制数字;\w能够用于表示全部字母数字的字符集,相当于[A-Za-z0-9_]的缩写形式;\s可以用来表示空格字符。这些字符的大写表示不匹配,如\D表示任何非十进制数(与[^0-9]相同) |
1.8 使用圆括号指定分组
正则表达式模式 | 匹配的字符串 |
\d+(\.\d*)? | 表示简单浮点数的字符串;也就是说,任何十进制数字,后面可以接一个小数点和零个或者多个十进制数字,例如0.004、2、75. |
(Mr?s?\.)?[A-Z][a-z]*[A-Za-z-]+ | 名字和姓氏,以及对名字的限制(如果有,首字母必须大写,后续字母小写),全名前可以有可选的“Mr.”、“Mrs.”、“Ms.”或者“M.”作为称谓,以及灵活可选的姓氏,可以有多个单词、横线以及大写字母 |
使用圆括号,我们可以知道整个字符串是否匹配我们的标准,而且还能提取任何已经成功匹配的特定字符串或者子字符串。
|
1.9 扩展表示法
正则表达式模式 | 匹配的字符串 |
(?:\w+\.)* | 以句点作为结尾的字符串,例如“google.”、“twitter.”,但是这些匹配不会保存下来供后续的使用和数据检索 |
(?#comment) | 此处并不做匹配,知识作为注释 |
(?=.com) | 如果一个字符串后面跟着“.com”才做匹配操作,并不使用任何目标字符串 |
(?!.net) | 如果一个字符串后面不是跟着“.net”才做匹配操作 |
(?<=800-) | 如果字符串之前为“800-”才做匹配,假定为电话号码,同样,并不使用任何输入字符串 |
(? | 如果一个字符串之前不是“192.168.”才做匹配操作,假定用于过滤掉一组C类IP地址 |
(?(1)y|x) | 如果一个匹配组1(\1)存在,就与y匹配;否则,就与x匹配 |
以问号开始(?...)通常用于在判断匹配之前提供标记,实现一个前视(或者后视)匹配,或者条件检查 |
二、正则表达式和Python语言
函数/方法 | 描述 | |
仅仅是re模块函数 | ||
complie(pattern, flags) | 使用任何可选的标记来编译正则表达式的模式,然后返回一个正则表达式对象 | |
re模块函数和正则表达式对象的方法 | ||
match(pattern, string, flags) | 尝试使用带有可选的标记的正则表达式的模式来匹配字符串。如果匹配成功,就返回匹配对象;如果失败,就返回None | |
search(pattern, string, flags) | 使用可选标记搜索字符串中第一次出现的正则表达式模式。如果匹配成功,则返回匹配对象;如果失败,返回None | |
findall(pattern, string, flags) | 查找字符串中所有(非重复)出现的正则表达式模式,并返回一个匹配列表 | |
finditer(pattern, string, flags) | 与findall()函数相同,但返回的不是一个列表,而是一个迭代器。对于每一次匹配,迭代器都返回一个匹配对象 | |
split(pattern, string, maxsplit, flags) | 根据正则表达式的模式分隔符,split函数将字符串分割为列表,然后返回成功匹配的列表,分隔最多操作max次(默认分割所有匹配成功的位置) | |
re模块函数和正则表达式对象方法 | ||
sub(pattern, repl, string, count, flags) | 使用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 | 通过反斜线转义,否则所有空格加上#(以及在该行中所有后续文字)都被忽略,除非在一个字符类中或者允许注释并且提高可读性 |
2.1 使用compile()函数编译正则表达式
import re pattern = re.compile('[a-zA-Z0-9_]', re.S) result = pattern.findall('5678jb@#$%/./"_') print (result)
2.2 匹配对象以及group()和groups()方法
匹配成功调用match()或者search()返回的对象的俩个主要方法就是group()和groups().
pat = re.compile(r'www\.(.*)\.(.*)') #用()表示1个组,2个组 m = pat.match('www.dxy.com') m.group() #默认为0,表示匹配整个字符串 'www.dxy.com' m.group(1) #返回给定组1匹配的子字符串 'dxy' m.group(2) 'com' m.groups() ('dxy', 'com')
2.3 使用match()方法匹配字符串
import re m1 = re.match('foo', 'foo') m2 = re.match('foo', 'bar') if m1 is not None: print(m1) # <re.Match object; span=(0, 3), match='foo'> print(m1.group()) # foo if m2 is not None: print(m2.group())
为了简洁起见,可省略if语句块,但在实际操作中避免该操作,否则引起AttributeError异常
2.4 使用search()在一个字符中查找模式(搜索与匹配的对比)
search()的工作方式与match()完全一致,不同之处在于search会用它的字符串参数,在任意位置对给定正则表达式模式搜索第一次出现的匹配情况。如果搜索到成功的匹配,就会返回一个匹配对象;否则,返回None。
举例:
import re m1 = re.match('foo', 'seafood') # 匹配失败 if m1 is not None: print(m1.group()) m2 = re.search('foo', 'seafood') # 搜索成功,但匹配失败 if m2 is not None: print(m2.group())
说明:
match()试图从字符串的起始部分开始匹配模式,模式中的“f”将匹配到字符串的首字母“s”上,这将匹配失败。所以我们需要search()函数,而不是尝试匹配。search()函数不但会搜索模式在字符串中第一次出现的位置,而且严格地对字符串从左到右搜索。
2.5 匹配多个字符串
import re bt = 'bat|bet|bit' m1 = re.match(bt, 'blt') # 匹配失败 if m1 is not None: m1.group() m2 = re.match(bt, 'He bit me!') # 不能匹配字符串 if m2 is not None: m2.group() m3 = re.search(bt, 'He bit me!') # 通过搜索查找'bit' if m3 is not None: m3.group()
2.6 匹配任何单个字符
句号(.)不能匹配一个换行符或者空字符串
import re anyend = '.end' m1 = re.match(anyend, 'bend') # 点号匹配b if m1 is not None: print(m1.group()) m2 = re.match(anyend, 'end') # 不匹配任何字符 if m2 is not None: print(m2.group()) m3 = re.match(anyend, '\nend') # 除了\n之外的任何字符 if m3 is not None: print(m3.group()) m4 = re.search(anyend, 'The end.') # 在搜索中匹配' ' if m4 is not None: print(m4.group())
如果要匹配小数点,我们可以使用反斜线来进行转义
import re patt314 = '3.14' pi_patt = '3\.14' m1 = re.match(pi_patt, '3.14') # 精确匹配 if m1 is not None: print(m1.group()) m2 = re.match(patt314, '3014') # 点号匹配'0' if m2 is not None: print(m2.group()) m3 = re.match(patt314, '3.14') # 点号匹配'.' if m3 is not None: print(m3.group())
2.7 匹配字符集
import re bt = '[cr][23][dp][o2]' m = re.match(bt, 'c3po') # 匹配失败 if m is not None: print(m.group())
2.8 特殊字符以及分组
import re patt = '\w+@(\w+\.)*\w+\.com' # 允许任意数量的中间子域存在 re.match(patt, 'nobody@www.xxx.yyy.com').group() m = '(\w\w\w)-(\d\d\d)' re.match(m, 'abc-123').group() v1 = re.match('ab', 'ab') print(v1.groups()) # () v2 = re.match('(ab)', 'ab') print(v2.groups()) # ('ab',) v3 = re.match('(a)(b)', 'ab') print(v3.groups()) # ('a','b') v4 = re.match('(a(b))', 'ab') print(v4.groups()) # ('ab', 'b')
2.9 匹配字符串的起始和结尾以及单词边界
import re m1 = re.search('^The', 'The end.') # 匹配 if m1 is not None: print(m1.group()) m2 = re.search('^The', 'end. The') # 不作为起始 if m2 is not None: print(m2.group()) m3 = re.search(r'\bthe', 'bite the dog') # 在边界 if m3 is not None: print(m3.group()) m4 = re.search(r'\bthe', 'bitethe dog') # 有边界 if m4 is not None: print(m4.group()) m5 = re.search(r'\Bthe', 'bitethe dog') # 没有边界 if m5 is not None: print(m5.group())
^和\b操作符更多用于表示搜索而不是匹配,因为match()总是从字符串开始位置进行匹配。
2.10 使用findall()和finditer()查找每一次出现的位置
findall()查询字符串中某个正则表达式模式全部的非重复出现情况。这与search在执行字符串搜索时类似,但与match()和search()不同于,findall()总是返回一个列表。如果findall()没有找到匹配的部分,就返回一个空列表,但如果匹配成功,列表将包含所有成功的匹配部分(从左向右按出现顺序排列)
import re print(re.findall('car', 'scary')) print(re.findall('car', 'carry the barcardi to the car'))
finditer()函数与findall()函数类似但是更节省内存的变体。俩者之间以及和其他变体函数之间的差异(很明显不同于返回的时一个迭代器还是列表)在于,和返回的匹配字符串相比,finditer()在匹配对象中迭代。
import re s = 'This and that.' print(re.findall(r'(th\w+) and (th\w+)', s, re.I)) # [('This', 'that')] print(re.finditer(r'(th\w+) and (th\w+)', s, re.I).__next__().groups()) # ('This', 'that') print(re.finditer(r'(th\w+) and (th\w+)', s, re.I).__next__().group(1)) # 'This' [print(g.groups()) for g in re.finditer(r'(th\w+) and (th\w+)', s, re.I)] # ('This', 'that') print(re.findall(r'(th\w+)', s, re.I)) # [('This', 'that')] print(re.finditer(r'(th\w+)', s, re.I).__next__().groups()) # ['This', 'that'] print(re.finditer(r'(th\w+)', s, re.I).__next__().group(1)) # ('This',) [print(g.groups(1)) for g in re.finditer(r'(th\w+)', s, re.I)] # ('that',)
注意,使用finditer()函数完成的所有额外工作都旨在获取它的输出来匹配findall()的输出。
findall()和finditer()方法的版本支持可选的pos和endpos参数,这俩个参数用于控制目标字符串的·搜索边界。
2.11 使用sub()和subn()搜索与替换
sub()和subn()都是将某字符串中所有匹配正则表达式的部分进行某种形式的替换,但sunbn()还返回一个表示替换的总数,替换后的字符串和表示替换总数的数字一起作为一个拥有两个元素的元组返回。
import re print(re.sub('X', 'Mr.Smith', 'attn: X\n\nDear X,\n')) ''' attn: Mr.Smith Dear Mr.Smith, ''' print(re.subn('X', 'Mr.Smith', 'attn: X\n\nDear X,\n')) # ('attn: Mr.Smith\n\nDear Mr.Smith,\n', 2) print(re.sub('[ae]', 'X', 'abcdef')) # XbcdXf print(re.subn('[ae]', 'X', 'abcdef')) # ('XbcdXf', 2)
2.12 在限定模式上使用split()分隔字符串
import re print(re.split(':', 'str1:str2:str3')) # ['str1', 'str2', 'str3']
import re DATA = { 'Mountain View, CA 94040', 'Sunnyvale, CA', 'Los Altos, 94023', 'Cupertino 95014', 'Palo Alto CA', } for datum in DATA: print(re.split(', |(?= (?:\d{5}|[A-Z]{2})) ', datum))
2.13 扩展符号
re.I/re.IGNORECASE与re.M/MULTILINE实现多行混合
import re print(re.findall(r'(?i)yes', 'yes? Yes. YES!')) # ['yes', 'Yes', 'YES'] print(re.findall(r'(?im)(^th[\w ]+)',""" This line is the first, another line, that line, it's the best """)) # ['This line is the first', 'that line']
re.S/DOTALL表明点好(.)能够用来表示\n符号(反之其通常用于表示除了\n之外的全部字符)
import re print(re.findall(r'th.+',''' The first line the second line the third line ''')) # ['the second line', 'the third line'] print(re.findall(r'(?s)th.+',''' The first line the second line the third line ''')) # ['the second line\nthe third line\n']
re.X/VERBOSE允许用户通过抑制在正则表达式中使用空白符来创建更易读的正则表达式。
import re print(re.search(r'''(?x) \((\d{3})\) [ ] (\d{3}) - (\d{4}) ''', '(800) 555-1212').groups())
当不想保存今后永远不会使用的多余匹配时,我们可以使用(?:…)
import re print(re.findall(r'http://(?:\w+\.)*(\w+\.com)', 'http://www.baidu.com http://baidu.com')) # ['baidu.com', 'baidu.com'] print(re.search(r'\((?P<areacode>\d{3})\) (?P<prefix>\d{3})-(?:\d{4})', '(800) 555-1212').groupdict()) # {'areacode': '800', 'prefix': '555'}
我们可以一起使用(?P)和(?P=name)。前者通过使用一个名称标识符而不是使用从1开始增加到N的增量数字来保存匹配,如果使用数字来保存匹配结果,我们就可以通过\1,\2…,\N来检索。使用后者,可以在一个相同的正则表达式中重用模式,而不必稍后再次在(相同)正则表达式中指定相同的模式。
import re print(re.sub(r'\((?P<areacode>\d{3})\) (?P<prefix>\d{3})-(?:\d{4})', '(\g<areacode>) \g<prefix>-xxxx', '(800) 555-1212')) # (800) 555-xxxx
import re print(bool(re.match(r'\((?P<areacode>\d{3})\) (?P<prefix>\d{3})-(?P<number>\d{4}) (?P=areacode)-(?P=prefix)-(?P=number) 1(?P=areacode)(?P=prefix)(?P=number)', '(800) 555-1212 800-555-1212 18005551212'))) # True print(bool(re.match(r'''(?x) \((?P<areacode>\d{3})\) [ ] (?P<prefix>\d{3})-(?P<number>\d{4}) [ ] (?P=areacode)-(?P=prefix)-(?P=number) [ ] 1(?P=areacode)(?P=prefix)(?P=number) ''','(800) 555-1212 800-555-1212 18005551212'))) # True
我们可以使用(?=…)和(?!..)在目标字符串中实现一个前视匹配,而不必实际上使用这些字符串。
import re print(re.findall(r'\w+(?= van Rossum)', ''' Guido van Rossum Tim Peters Alex Martelli Just van Rossum Raymond Hettinger ''')) # ['Guido', 'Just'] print(re.findall(r'(?m)^\s+(?!noreply|postmaster)(\w+)', ''' sales@phptr.com postmaster@phptr.com eng@phptr.com noreply@phptr.com admin@phptr.com ''')) # ['sales', 'eng', 'admin'] print(['%s@aw.com' % e.group(1) for e in \ re.finditer(r'(?m)^\s+(?!noreply|postmaster)(\w+)', ''' sales@phptr.com postmaster@phptr.com eng@phptr.com noreply@phptr.com admin@phptr.com ''')]) # ['sales@aw.com', 'eng@aw.com', 'admin@aw.com']
以下示例展示了使用条件正则表达式匹配
import re print(bool(re.sub(r'\((?P<areacode>\d{3})\) (?P<prefix>\d{3})-(?P<number>\d{4})', '(\g<areacode>) \g<prefix>-xxxx', '(800) 555-1212'))) # True
三、正则表达式示例
whodata.txt文件中保存一些用户的登陆信息,如登录名、用户登陆终端类型、用户登录的时间和地点
wesley console Jun 20 20:33 wesley pts/9 Jun 22 01:38 (192.168.0.6) wesley pts/1 Jun 20 20:33 (:0.0) wesley pts/2 Jun 20 20:33 (:0.0) wesley pts/4 Jun 20 20:33 (:0.0) wesley pts/3 Jun 20 20:33 (:0.0) wesley pts/5 Jun 20 20:33 (:0.0) wesley pts/6 Jun 20 20:33 (:0.0) wesley pts/7 Jun 20 20:33 (:0.0) wesley pts/8 Jun 20 20:33 (:0.0)
如果我们想使用split()方法并不高效,因为文本中的空白符既不稳定也不一致,而且在登录时间戳中间的月、日和时间之间有空格。如果我们需要保存连续的字段,可以首先考虑模式\s\s+(至少拥有俩个以上的空白符),运行代码结果如下:
import re f = open('whodata.txt', 'r') for eachline in f: print(re.split(r'\s\s+', eachline)) f.close()
['wesley', 'console', 'Jun 20 20:33\n'] ['wesley', 'pts/9', 'Jun 22 01:38\t(192.168.0.6)\n'] ['wesley', 'pts/1', 'Jun 20 20:33\t(:0.0)\n'] ['wesley', 'pts/2', 'Jun 20 20:33\t(:0.0)\n'] ['wesley', 'pts/4', 'Jun 20 20:33\t(:0.0)\n'] ['wesley', 'pts/3', 'Jun 20 20:33\t(:0.0)\n'] ['wesley', 'pts/5', 'Jun 20 20:33\t(:0.0)\n'] ['wesley', 'pts/6', 'Jun 20 20:33\t(:0.0)\n'] ['wesley', 'pts/7', 'Jun 20 20:33\t(:0.0)\n'] ['wesley', 'pts/8', 'Jun 20 20:33\t(:0.0)']
但是我们不期望垂直制表符(ASCII\011)作为输出的部分,虽然它看起来像多个空白符;同时我们也不希望保存\n(ASCII\012)作为每一行的终止符。去掉尾部的\n可以使用str.strip()。
import re f = open('whodata.txt', 'r') for eachline in f: print(re.split(r'\s\s+|\t|\n', eachline.strip())) f.close()
['wesley', 'console', 'Jun 20 20:33'] ['wesley', 'pts/9', 'Jun 22 01:38', '(192.168.0.6)'] ['wesley', 'pts/1', 'Jun 20 20:33', '(:0.0)'] ['wesley', 'pts/2', 'Jun 20 20:33', '(:0.0)'] ['wesley', 'pts/4', 'Jun 20 20:33', '(:0.0)'] ['wesley', 'pts/3', 'Jun 20 20:33', '(:0.0)'] ['wesley', 'pts/5', 'Jun 20 20:33', '(:0.0)'] ['wesley', 'pts/6', 'Jun 20 20:33', '(:0.0)'] ['wesley', 'pts/7', 'Jun 20 20:33', '(:0.0)'] ['wesley', 'pts/8', 'Jun 20 20:33', '(:0.0)']
四、更长的正则表达式
from random import randrange, choice from string import ascii_lowercase as lc from time import ctime tlds = ('com', 'edu', 'net', 'org', 'gov') for i in range(randrange(5, 11)): dtint = randrange(2**32) # pick date dtstr = ctime(dtint) # date string llen = randrange(4, 8) # login is shorter login = ''.join(choice(lc) for j in range(llen)) dlen = randrange(llen, 13) # domain is longer 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 28 16:07:28 2027::zhveq@fgxsoorfp.org::1814170048-5-9 Mon Sep 3 01:27:46 2057::zvunm@xszslt.edu::2766677266-5-6 Tue May 27 14:43:41 1975::upink@ewcjka.org::170405021-5-6 Mon Apr 15 14:51:29 1985::yqcl@qhdg.gov::482395889-4-4 Tue Jan 7 01:03:52 2053::dywndef@xiemily.gov::2619795832-7-7 Sat Oct 22 04:02:55 2101::syke@dyttqo.edu::4159368175-4-6 Sat Nov 21 01:44:57 1970::fkpyhba@neqpacw.gov::27971097-7-7 Tue Nov 16 00:31:29 2094::jlqfs@yiwpsg.org::3940677089-5-6 Wed Feb 13 09:39:51 2058::nzupfjr@ybcxyxyuccd.gov::2780789991-7-11 Thu Jan 4 20:39:14 2080::xbrua@nzzzertwgs.edu::3471597554-5-10
第五行:
tlds是一组高级域名集合,当需要随机生成电子邮件地址时,就可以从中随机选出一个
第七到第九行:
对于每一行,我们选取所有可能范围(0~231-1)中的随机整数,然后使用time.ctime()函数将该整数转换为日期。Python中的系统时间和大多数基于POSIX的计算机一样,俩者都使用从"epoch"至今的秒数,epoch是指1970年1月1日格林威治时间的午夜。如果我们选择一个32位整数,那么该整数将表示从epoch到最大可能时间(即epoch后的232秒)之间的某个时刻
第十到十三行:
伪造邮件地址的登录名长度为4~7个字符,主域名长度不能多于12个字符,并将它们逐个连接成一个字符串。random.choice()函数的功能就是接受一个序列,然后返回该序列中的一个随机元素。string.ascii_lowercase是字母表中拥有26个小写字母的序列集合。
第十四行:
将所有随机数据放入输出行。
4.1 匹配字符串
提起时间戳中一周的星期
import re data = 'Tue Jan 7 01:03:52 2053::dywndef@xiemily.gov::2619795832-7-7' patt = '^(Mon|Tue|Wed|Thu|Fri|Sat|Sun)' m = re.match(patt, data) if m is not None: print(m.group()) # Tue print(m.group(1)) # Tue print(m.groups()) # ('Tue',)
4.2 搜索与匹配。。。还有贪婪
我们想要寻找三个由连字符分隔的整数,我们可以使用正则表达式\d±\d±\d.我们现在使用search()来测试该正则表达式
import re data = 'Tue Jan 7 01:03:52 2053::dywndef@xiemily.gov::2619795832-7-7' patt = '\d+-\d+-\d' m = re.search(patt, data) if m is not None: print(m.group()) # 2619795832-7-7
但是我们匹配失败了。因为匹配从字符串的起始部分开始,但我们被匹配的数值位于字符串的末尾。可以使用惰性匹配,即使用“.+”来表明字符集跟在我们真正感兴趣的部分之后。
import re data = 'Tue Jan 7 01:03:52 2053::dywndef@xiemily.gov::2619795832-7-7' patt = '.+\d+-\d+-\d' m = re.match(patt, data) if m is not None: print(m.group()) # Tue Jan 7 01:03:52 2053::dywndef@xiemily.gov::2619795832-7-7
遗憾的是我们只想要末尾的数字字段,而不是整个字符串,因此需要使用圆括号对想要的内容进行分组
import re data = 'Tue Jan 7 01:03:52 2053::dywndef@xiemily.gov::2619795832-7-7' patt = '.+(\d+-\d+-\d)' m = re.match(patt, data) if m is not None: print(m.group(1)) # 2-7-7
我们想要提取的是2619795832-7-7,但是获取的确是2-7-7,为什么?问题在于正则表达式本质上实现贪婪匹配。这意味着对于该通配符模式,将对正则表达式从左至右按顺序求值,而且试图获取匹配该模式的尽可能多的字符。
为了解决这个问题,其中一个方案是使用“非贪婪”操作符“?”。我们可以在*、+或者?之后使用该操作符。该操作符将要求正则表达式引擎匹配尽可能少的字符。
另一个方案就是把“::”作为字段分隔符。我们可以仅仅使用正则字符串strip(’::’)方法获取所有部分,然后使用strip(’-’)作为另一个横线分隔符,就能够获取最初想要查询的三个整数。
import re data = 'Tue Jan 7 01:03:52 2053::dywndef@xiemily.gov::2619795832-7-7' patt = '-(\d+)-' m = re.search(patt, data) if m is not None: print(m.group()) # -7- print(m.group(1)) # 7