#1、BRE
基本正则表达式,grep、sed、vi等软件支持。vim有扩展。
2、ERE
扩展正则表达式,egrep(grep -E)、sed -r等。
3、PCRE
几乎所有高级语言都是PCRE的方言或变种。
Python从1.6使用SRE正则表达式引擎,可以认为是PCRE的子集,见模块re。
元字符:
. 匹配除换行符外任意一个字符
import re
s = 'very'
regex = re.compile('.')
matcher = regex.match(s)
print(matcher)
<_sre.SRE_Match object; span=(0, 1), match='v'>
[abc] 字符集合,只能表示一位字符位置,匹配所包含的任意一个字符
[^abc] 字符集合,只能表示一位字符位置,匹配除去集合内字符的任意一个字符
[a-z] 字符范围,也是个集合,表示一个字符位置,匹配所包含的任意一个字符 常用[A-Z],[0-9]
[^a-z] 字符范围,也是个集合,表示一个字符位置,匹配除去集合内字符的任意一个字符
\b 匹配单词的边界 \bb 在文件中找到单词中b开头的b字符
例如 \bb. # verybi beautiful 匹配be
\B 不匹配单词的边界 t\B包含t的单词但是不以t结尾的t字符 例如write
\Bb 不以b开头的含有b的单词,例如able
\d [0-9]匹配1位数字
\D [^0-9]匹配一位非数字
\s 匹配一位空白字符,包括换行符、制表符、空格 [\f\r\n\t\v]
\S 匹配一位非空白字符
\w 匹配[a-zA-Z0-9_],包括中文的字
\W 匹配\w之外的字符
单行模式:
.可以匹配所有字符,包括换行符
^表示整个字符串的开头,$整个字符串的结尾
多行模式:
.可以匹配除了换行符之外的字符
^表示行首,$行尾
注意:字符串中看不见的换行符,\r\n会影响e$的测试,e$只能匹配e\n
举例:
very very happy
harry key
y$ 单行匹配key的y,多行匹配happy和key的y。
转义:
凡是在正则表达式中有特殊意义的符号,如果想使用它的本意,请使用\转义,反斜杠自身用\\
重复:
* 表示前面的正则表达式会重复0次或多次 e[a-z]*单词中有e然后后面a-z一位字符,0次或多次
+ 表示前面的正则表达式重复至少1次 e[a-z]+单词中有e然后后面a-z一位字符,至少有一个
? 表示前面的正则表达式会重复0次或1次 e[a-z]?单词中有e然后后面a-z一位字符,会重复0次或1次
{n}重复固定的n次 e[a-z]{2}单词中e后面只能有a-z一位字符,2次
{n,}重复至少n次 e[a-z]{1,} = e[a-z]+ e[a-z]{0,} = e[a-z]* e[a-z]{0,1} = e[a-z]?
{n,m}重复n到m次 e[a-z]{1,10}单词中e后面至少1个,至多10个a-z一位字符
匹配手机号码:1\d{10}
匹配座机:^0\d{2,3}-\d{7,8}
x|y 匹配x或者y wood took foot food 使用w|food 或者(w|f)ood
(pattern) 分组
\数字 匹配对应的分组 (very)\1匹配veryvery,但捕获的分组是very
零宽断言
(?=exp) 断言exp一定在匹配的右边出来,也就是说断言后面一定跟个exp
f(?=oo) f后面一定有oo出现
(?<=exp) 断言exp一定出现在匹配的左边 (?<=f)ood、(?<=t)ook 分别匹配ood前有f,ook前有t
负向零宽断言
(?!exp) 断言exp一定不会出现在右侧,也就是说断言后面一定不是exp \d{3}(?!\d) 断言三位数字后面一定不是数字
(?<!exp) 断言exp一定不会出现在左侧,也就是说断言前面一定不是exp (?<!f)ook
(?#comment) 注释 f(?=oo)(?#这个后断言不捕获)
断言不占分组号
贪婪与非贪婪
*? 匹配任意次,但尽量可能少重复
+? 匹配至少一次,但尽量可能少重复
?? 匹配0次到1次,但尽量可能少重复
{n,}? 匹配至少n次,但尽量可能少重复
{n,m}? 匹配至少n次,至多m次,但尽量可能少重复
very very 使用v.*y v.*?y
引擎选项:
IgnoreCase 匹配时忽略大小写 py: re.I re.IGNORECASE
Singleline 单行模式:可以匹配所有字符,包括\n py:re.S re.DOTALL
Multiline 多行模式:^行首 $行尾 py:re.M re.MULTILINE
练习匹配一个0~999之间的任意数字
1
23
886
一位数:\d
二位数:[1-9]?\d
三位数:^([1-9]\d\d?|\d)$ 也可以这样:^[1-9]?\d\d?$
ip地址:(?:(25[0-5]|2[0-4]\d|[01]?\d\d?)\.){3}(?:25[0-5]|2[0-4]\d|[01]?\d\d?)
也可以通过IP地址解析库处理:
import socket
nw = socket.inet_aton('255.255.255.255')
print(nw, socket.inet_ntoa(nw))
b'\xff\xff\xff\xff' 255.255.255.255
选出含有ftp的链接,且文件类型是gz或者xz的文件名
.*ftp.*\.(gz|xz)
改进
.*ftp.*/([^/]*\.(?:gz|xz)) #捕获文件名分组
终版
(?<=.*ftp.*/)[^/]*\.(?:gz|xz) 蓝色的左边一定出现红色表达式
编译:
re.compile(pattern,flags=0)
可以设定flags,编译模式,返回正则表达式对象regex
pattern就是正则表达式字符串,flags是选项,正则表达式都需要被编译,为了提高效率
编译结果会被保存,下次用同样的pattern的时候,就不要再编译了
import re
s = '0123abc'
regex = re.compile('\d')
matcher = re.match('\d', s) #只从头开始找 一匹配到就不往后找了 并且还显示找到的位置
print(type(matcher))
print(1, matcher)
matcher = re.match('a', s) #只从0开始,不是就不找了
print(2, matcher)
matcher = regex.match(s) #与1一样
print(3, type(matcher))
print(4, matcher)
matcher = regex.match(s, 3) #可以选择起始位置查找
print(5, type(matcher))
print(6, matcher)
regex1 = re.compile('[ab]') #有时,并不能确定起始位置,search不从头找,找到第一个就返回
matcher = regex1.search(s)
print(7, type(matcher))
print(8, matcher)
matcher = regex1.search(s, 5) #也可以定义起始位置,减少搜索时间
print(9, type(matcher))
print(10, matcher)
matcher = re.fullmatch('\w+', s) #全匹配才能出来
print(11, matcher)
regex2 = re.compile('[ab]')
matcher = regex2.match(s, 5, 6) #这里就是匹配一位a或b 从5开始可以配置到b
print(12, matcher)
#
<class '_sre.SRE_Match'>
1 <_sre.SRE_Match object; span=(0, 1), match='0'>
2 None
3 <class '_sre.SRE_Match'>
4 <_sre.SRE_Match object; span=(0, 1), match='0'>
5 <class '_sre.SRE_Match'>
6 <_sre.SRE_Match object; span=(3, 4), match='3'>
7 <class '_sre.SRE_Match'>
8 <_sre.SRE_Match object; span=(4, 5), match='a'>
9 <class '_sre.SRE_Match'>
10 <_sre.SRE_Match object; span=(5, 6), match='b'>
11 <_sre.SRE_Match object; span=(0, 7), match='0123abc'>
12 <_sre.SRE_Match object; span=(5, 6), match='b'>
以上都建议先编译,再使用的方法
match、search、fullmatch
import re
s = '''bottle\nbag\nnbig\napple'''
for x in enumerate(s):
if x[0] % 8 == 0:
print()
print(x, end=' ')
# match方法
print('--match--')
result = re.match('b', s)
print(1, result)
result = re.match('a', s)
print(2, result) # 没找到,返回None
result = re.match('^a', s, re.M) # 依然从头开始找
print(3, result)
result = re.match('^a', s, re.S) # 依然从头开始找
print(4, result)
# 先编译,用正则表达式对象
regex = re.compile('a')
result = regex.match(s) # 依然从头开始找
print(5, result)
result = regex.match(s, 16) # 从索引16开始找
print(6, result)
# search方法
print('--search--')
result = re.search('a', s) # 从头搜索至到第一个配置
print(7, result)
regex = re.compile('b')
result = regex.search(s, 1)
print(8, result)
regex = re.compile('^b', re.M)
result = regex.search(s) # 不管是不是多行,找到第一个就返回
print(9, result) # bottle的b
regex = re.compile('b', re.M)
result = regex.search(s, 8)
print(10, result) # big
# fullmatch方法
result = re.fullmatch('bag', s)
print(11, result)
regex = re.compile('bottle\nbag\nnbig\napple')
result = regex.fullmatch(s)
print(12, result)
(0, 'b') (1, 'o') (2, 't') (3, 't') (4, 'l') (5, 'e') (6, '\n') (7, 'b')
(8, 'a') (9, 'g') (10, '\n') (11, 'n') (12, 'b') (13, 'i') (14, 'g') (15, '\n')
(16, 'a') (17, 'p') (18, 'p') (19, 'l') (20, 'e')
--match--
1 <_sre.SRE_Match object; span=(0, 1), match='b'>
2 None
3 None
4 None
5 None
6 <_sre.SRE_Match object; span=(16, 17), match='a'>
--search--
7 <_sre.SRE_Match object; span=(8, 9), match='a'>
8 <_sre.SRE_Match object; span=(7, 8), match='b'>
9 <_sre.SRE_Match object; span=(0, 1), match='b'>
10 <_sre.SRE_Match object; span=(12, 13), match='b'>
11 None
12 <_sre.SRE_Match object; span=(0, 21), match='bottle\nbag\nnbig\napple'>
全部匹配
re.findall、re.finditer
import re
s = '''bottle\nbag\nnbig\nable'''
regex = re.compile('[ab]')
print('--findall--')
result = re.findall('b', s)
print(1, result)
matcher = regex.findall(s)
print(1.5, type(matcher))
print(1.8, matcher)
result = re.findall('a', s)
print(2, result)
result = re.findall('^b\w+', s, re.M) #3与3.5意义相同
print(3, result)
regex = re.compile('^b\w+', re.M)
result = regex.findall(s)
print(3.5, result)
result = re.findall('^a', s, re.S)
print(4, result)
result = re.findall('^b', s, re.S)
print(5, result)
result = regex.findall(s, 7, 10)
print(6, result)
s = '0123abc'
regex1 = re.compile('[abc20]+') # + : 表示前面的正则表达式重复至少出现1次
matcher1 = regex1.findall(s)
print(matcher1)
regex = re.compile('\D')
matcher = regex.findall(s)
print('a', matcher)
print(type(matcher))
matcher2 = regex.finditer(s)
print('b', matcher2)
print(type(matcher2))
1 ['b', 'b', 'b', 'b']
1.5 <class 'list'>
1.8 ['b', 'b', 'a', 'b', 'a', 'b']
2 ['a', 'a']
3 ['bottle', 'bag', 'big']
3.5 ['bottle', 'bag', 'big']
4 []
5 ['b']
6 ['b', 'a']
['0', '2', 'abc']
a ['a', 'b', 'c']
<class 'list'>
b <callable_iterator object at 0x00000121F1ADA8D0>
<class 'callable_iterator'>
<_sre.SRE_Match object; span=(4, 5), match='a'>
<_sre.SRE_Match object; span=(5, 6), match='b'>
<_sre.SRE_Match object; span=(6, 7), match='c'> #多次match的结果 返回的是match对象
'r'是防止字符转义的 如果路径中出现'\t'的话 不加r的话\t就会被转义 而加了'r'之后'\t'就能保留原有的样子
在字符串赋值的时候 前面加'r'可以防止字符串在时候的时候不被转义 原理是在转义字符前加'\'
s=r'\tt'
print(s)
Output:
'\tt'
s='\tt'
print(s)
Output:
' t'
import re
s = '''bottle\nbag\nbig\nable'''
regex = re.compile(r'\bb\w+')
#这里要加上原始字符r/R,原因是:python默认会把\b解码给ascii码8(退格符)
matcher = regex.findall(s)
print(matcher)
['bottle', 'bag', 'big']
匹配替换
import re
s = '''bottle\nbag\nbig\napple'''
regex = re.compile('b\wg')
result = regex.sub('magedu', s)
print(1, result)
result = regex.sub('magedu', s, 1) # 替换1次
print(2, result)
regex = re.compile('\s+')
result = regex.subn('\t', s)
print(3, result)
1 bottle
magedu
magedu
apple
2 bottle
magedu
big
apple
3 ('bottle\tbag\tbig\tapple', 3)
分割字符串
import re
s = '''01 bottle
02 bag
03 big123
100 able'''
for x in enumerate(s):
if x[0] % 8 == 0:
print()
print(x, end=' ')
print('\n')
print(0, s.split()) #做不到
result = re.split('[\s\d]+', s) #这样big1 变成big不是想到的 下面改进
print(1, result)
regex = re.compile('^[\s\d]+') # 字符串首 只作用于第一行 下面再改进
result = regex.split(s)
print(2, result)
regex = re.compile('^[\s\d]+', re.M) # 行首 用多行模式
result = regex.split(s)
print(3, result)
regex = re.compile('\s+|(?<!\w)\d+') # 终版改进
result = regex.split(s)
print(4, result)
regex = re.compile('\s+\d+\s+') # 在最前面先加个空 然后以(空+数字+空)来分割
result = regex.split(' '+s) # 效率最高
print(5, result)
0 ['01', 'bottle', '02', 'bag', '03', 'big123', '100', 'able']
1 ['', 'bottle', 'bag', 'big', 'able']
2 ['', 'bottle\n02 bag\n03 big123\n100 able']
3 ['', 'bottle\n', 'bag\n', 'big123\n', 'able']
4 ['', '', 'bottle', '', '', 'bag', '', '', 'big123', '', '', 'able']
5 ['', 'bottle', 'bag', 'big123', 'able']
分组
编号从1开始
import re
s = '''bottle\nbag\nbig\nable'''
regex = re.compile(r'b(?P<test1>\w+)(?P<test2>e)') # 还可以用命名分组
# regex = re.compile(r'b(\w+)(?:e)') # 不捕获分组(e)
matchers = regex.finditer(s)
print(matchers)
for matcher in matchers:
print(matcher)
print(matcher.groupdict())
print(matcher.groups())
print(matcher.group(0))
print(matcher.group(1))
print(matcher.group(2))
print('----------------')
<callable_iterator object at 0x000001D23026CF60>
<_sre.SRE_Match object; span=(0, 6), match='bottle'>
{'test1': 'ottl', 'test2': 'e'}
('ottl', 'e')
bottle
ottl
e
----------------
<_sre.SRE_Match object; span=(16, 19), match='ble'>
{'test1': 'l', 'test2': 'e'}
('l', 'e')
ble
l
e
----------------
要非的方式来限定 只要不是"<"或">"的出现任意次
匹配身份证 最后用的断言 后面一定不是X或x或数字
或者:后面一定是跟的空
单词统计:
import re
from collections import defaultdict
regex = re.compile(r'[^\w-]+')
def wordcount(path):
d = defaultdict(lambda: 0)
with open(path, encoding='utf8') as f:
for line in f:
for j in regex.split(line):
if len(j) >= 1:
d[j.lower()] += 1
return d
for j in sorted(wordcount(r'E:\1.txt').items(), key=lambda x: x[1], reverse=True):
print(j)
#i = 0 打印出现频率最多的前十次
# for j in sorted(wordcount(r'E:\1.txt').items(), key=lambda x: x[1], reverse=True):
# if i < 10:
# print(j)
# i += 1
('the', 27)
('of', 15)
('to', 7)
('he', 5)
('on', 5)
('and', 4)
('french', 3)
('in', 3)
('after', 3)
('as', 3)
。
。
。
日志分析(初版):
import datetime
logline = '''123.125.71.36 - - [06/Apr/2017:18:09:25 +0800] \
"GET /o2o/media.html?menu=3 HTTP/1.1" 200 8642 "-" \
"Mozilla/5.0 (compatible; Baiduspider/2.0; +http://www.baidu.com/search/spider.html)"'''
def convert_time(timestr):
fmtstr = "%d/%b/%Y:%H:%M:%S %z"
dt = datetime.datetime.strptime(timestr, fmtstr)
print(555, type(dt))
return dt
def convert_request(request: str):
print(444, dict(zip(('method', 'url', 'protocol'), request.split())))
# keys = ['a', 'b', 'c'] #通过zip函数,再dict成一个字典 如:'method': 'GET'
# values = [1, 2, 3]
# dictionary = dict(zip(keys, values))
# print(dictionary)
#
# """
# 输出:
# {'a': 1, 'c': 3, 'b': 2}
# """
return dict(zip(('method', 'url', 'protocol'), request.split()))
names = ['remote', '', '', 'datetime', 'request', 'status', 'size', '', 'useragent']
ops = [None, None, None, convert_time, convert_request, int, int, None, None]
tmp = ""
fields = []
flag = False
for word in logline.split():
if not flag:
if word.startswith('[') or word.startswith('"'):
tmp = word[1:]
if word.endswith(']') or word.endswith('"'):
tmp = word.strip(']"')
fields.append(tmp)
else:
flag = True
else:
fields.append(word)
continue
if flag:
if word.endswith(']') or word.endswith('"'):
tmp += " " + word[:-1]
fields.append(tmp)
tmp = ""
flag = False
else:
tmp += " " + word
continue
print(111, fields)
d = {}
for i, field in enumerate(fields):
key = names[i]
print(222, ops[i], field)
if ops[i]:
d[key] = ops[i](field)
else:
d[key] = field
print(333, d)
111 ['123.125.71.36', '-', '-', '06/Apr/2017:18:09:25 +0800', 'GET /o2o/media.html?menu=3 HTTP/1.1', '200', '8642', '-', 'Mozilla/5.0 (compatible; Baiduspider/2.0; +http://www.baidu.com/search/spider.html)']
222 None 123.125.71.36
222 None -
222 None -
222 <function convert_time at 0x000001E463B95B70> 06/Apr/2017:18:09:25 +0800
555 <class 'datetime.datetime'>
222 <function convert_request at 0x000001E463B95EA0> GET /o2o/media.html?menu=3 HTTP/1.1
444 {'protocol': 'HTTP/1.1', 'url': '/o2o/media.html?menu=3', 'method': 'GET'}
222 <class 'int'> 200
222 <class 'int'> 8642
222 None -
222 None Mozilla/5.0 (compatible; Baiduspider/2.0; +http://www.baidu.com/search/spider.html)
333 {'': '-', 'useragent': 'Mozilla/5.0 (compatible; Baiduspider/2.0; +http://www.baidu.com/search/spider.html)', 'remote': '123.125.71.36', 'status': 200, 'size': 8642, 'datetime': datetime.datetime(2017, 4, 6, 18, 9, 25, tzinfo=datetime.timezone(datetime.timedelta(0, 28800))), 'request': {'protocol': 'HTTP/1.1', 'url': '/o2o/media.html?menu=3', 'method': 'GET'}}
改进版:
import re
import datetime
logline = '''123.125.71.36 - - [06/Apr/2017:18:09:25 +0800] \
"GET /o2o/media.html?menu=3 HTTP/1.1" 200 8642 "-" \
"Mozilla/5.0 (compatible; Baiduspider/2.0; +http://www.baidu.com/search/spider.html)"'''
def extract(line):
# 0.0.0.0 255.255.255.255
pattern = '''(?P<remote>[\d\.]{7,15}) - - \[(?P<datetime>[^\[\]]+)\] "(?P<request>[^"]+)" (?P<status>\d+) \
(?P<size>\d+) "([^"]+)" "(?P<useragent>[^"]+)"'''
regex = re.compile(pattern)
matcher = regex.match(line)
return matcher.groupdict()
# print(222, extract(logline))
def convert_time(timestr): # 可改为lambda
fmtstr = "%d/%b/%Y:%H:%M:%S %z"
dt = datetime.datetime.strptime(timestr, fmtstr)
# print(555, type(dt))
return dt
def convert_request(request: str): # 可改为lambda
# print(444, dict(zip(('method', 'url', 'protocol'), request.split())))
return dict(zip(('method', 'url', 'protocol'), request.split()))
ops = {'datetime': convert_time, # 替换为lambda
'request': convert_request, # 替换为lambda
'status': int,
'size': int
}
print('----------------------------------------------------------------')
d = {k: ops.get(k, lambda x: x)(v) for k, v in extract(logline).items()} # 字典表达式,此语作用等同下四句
# for k, v in extract(logline).items():
# # if ops.get(k):
# # d[k] = ops[k](v)
# d[k] = ops.get(k, lambda x: x)(v) # 此句作用等同上二句
for k, v in d.items():
print(k, v)
----------------------------------------------------------------
datetime 2017-04-06 18:09:25+08:00
useragent Mozilla/5.0 (compatible; Baiduspider/2.0; +http://www.baidu.com/search/spider.html)
request {'url': '/o2o/media.html?menu=3', 'protocol': 'HTTP/1.1', 'method': 'GET'}
remote 123.125.71.36
status 200
size 8642
再改进版:
import re
import datetime
logline = '''123.125.71.36 - - [06/Apr/2017:18:09:25 +0800] \
"GET /o2o/media.html?menu=3 HTTP/1.1" 200 8642 "-" \
"Mozilla/5.0 (compatible; Baiduspider/2.0; +http://www.baidu.com/search/spider.html)"'''
def extract(line):
pattern = '''(?P<remote>[\d\.]{7,15}) - - \[(?P<datetime>[^\[\]]+)\] "(?P<request>[^"]+)" (?P<status>\d+) \
(?P<size>\d+) "([^"]+)" "(?P<useragent>[^"]+)"'''
regex = re.compile(pattern)
matcher = regex.match(line)
return matcher.groupdict()
ops = {'datetime': lambda timestr: datetime.datetime.strptime(timestr, "%d/%b/%Y:%H:%M:%S %z"),
'request': lambda request: dict(zip(('method', 'url', 'protocol'), request.split())),
'status': int,
'size': int
}
d = {k: ops.get(k, lambda x: x)(v) for k, v in extract(logline).items()}
for k, v in d.items():
print(k, v)
status 200
datetime 2017-04-06 18:09:25+08:00
useragent Mozilla/5.0 (compatible; Baiduspider/2.0; +http://www.baidu.com/search/spider.html)
size 8642
remote 123.125.71.36
request {'method': 'GET', 'url': '/o2o/media.html?menu=3', 'protocol': 'HTTP/1.1'}
终版:
import re
import datetime
logline = '''123.125.71.36 - - [06/Apr/2017:18:09:25 +0800] \
"GET /o2o/media.html?menu=3 HTTP/1.1" 200 8642 "-" \
"Mozilla/5.0 (compatible; Baiduspider/2.0; +http://www.baidu.com/search/spider.html)"'''
pattern = '''(?P<remote>[\d\.]{7,15}) - - \[(?P<datetime>[^\[\]]+)\] "(?P<request>[^"]+)" (?P<status>\d+) \
(?P<size>\d+) "([^"]+)" "(?P<useragent>[^"]+)"'''
regex = re.compile(pattern)
def extract(line):
matcher = regex.match(line)
if matcher:
return {k: ops.get(k, lambda x: x)(v) for k, v in matcher.groupdict().items()}
ops = {'datetime': lambda timestr: datetime.datetime.strptime(timestr, "%d/%b/%Y:%H:%M:%S %z"),
'request': lambda request: dict(zip(('method', 'url', 'protocol'), request.split())),
'status': int,
'size': int
}
lines = []
for l in lines:
d = extract(l)
if d:
pass
else:
pass # 看看不合格的有多少