正则表达式
正则表达式缩写为regex、regexp、RE等。他是文本处理极为重要的技术,其应用非常广泛,shell中处理文本的命令、各种高级编程语言都支持正则表达式,用它可以对字符串按照某种规则进行检索和替换,
分类
- BRE:基本正则,grep、sed、vi等软件支持。vim支持扩展正则
- ERE:扩展正则,egrep(grep -E)、sed -r等。
- PCRE:几乎所有高级语言都是PCRE的变种。Python的RE可以认为是PCRE的子集
基本语法
- 元字符(matchcharacter)
代码 | 说明 | 举例 |
---|---|---|
. | 匹配除换行符外任意一个字符 | |
[abc] | 字符集合,只能表示一个字符位置。匹配所包含的任意一个字符 | [abc]匹配plain中的’a’ |
[^abc] | 字符集合,只能表示一个字符位置。匹配出去集合内字符的任意一个字符 | [^abc]可以匹配plain中p、l、i、n |
[a-z] | 字符范围,也是集合,只能表示一个字符位置。匹配集合所包含的任意一个字符 | |
[^a-z] | 字符范围,也是集合,只能表示一个字符位置。匹配除集合内字符的任意一个字符 | |
\b | 匹配单词边界 | \bb在文本中找到单词中b开头的b字符 |
\B | 不匹配单词的边界 | t\b包含t的单词但是不以t结尾的t字符,例如write;\Bb不以b开头的含有B的单词,例如table |
\d | [0-9]匹配1位数字 | |
\D | [^0-9]匹配一位非数字 | |
\s | 匹配1位空白字符,包含换行符、制表符、空格 | |
\S | 匹配一位非空白字符 | |
\w | 匹配[a-zA-Z0-9_],包括中文的字 | |
\W | 匹配\w之外的字符 |
- 单行模式:
.可以匹配所有字符,包括换行符 ^表示整个字符串开头,$表示整个字符串的结尾
- 多行模式:
.可匹配除换行符之外的字符 ^表示行收,$表示行尾 ^表示整个字符串的开始,$表示整个字符串的结尾。开始指的是\n后紧接着的下一个字符,结束指的是\n前的字符
注意:字符串中看不见的换行符,windows中的回车换行符\r\n会影响$的测试。
- 转义:凡是在正则表达式中有特殊意义的符号,如果想使用它的本意,请使用\转义。反斜杠自身,需使用\\;\r、\n是转义后代表回车、换行
- 重复
符号 | 说明 | 举例 |
---|---|---|
* | 表示前面的正则表达式会重复0次或多次 | e\w*单词e后面有0到任意个非空字符 |
+ | 表示前面的正则表达式重复至少一次 | e\w+单词e后至少有一个非空白字符 |
? | 表示前面的正则表达式会重复0或1次 | e\w?单词e后最多有一个非空白字符 |
{n} | 重复固定的n次 | e\w{1}单词e后面只能有一个非空白字符 |
{n,} | 重复至少n次 | e\w{1,}等价e\w+;\w{0,}等价e\w*;e\w{0,1}等价e\w? |
{n,m} | 重复至少n次,最多m次 | e\w{1,10}单词e后面至少1个,最多是个非空白字符 |
- 练习
- 匹配手机号码:\d{11}
- 匹配固定电话号码\d{3-4}-\d{7-8}
- 分组
代码 | 说明 |
---|---|
x|y | 匹配x或y |
(pattern) | 使用小括号制定一个子表达式,也叫分组。捕获后会自动分配组号从1开始,可以改变优先级 |
\数字 | 匹配对应分组 |
(?:pattern) | 如果仅仅为了改变优先级,就不需要捕获分组 |
(?<name>exp)(?’name’exp) | 分组捕获,但是可以通过name访问分组 |
零宽断言 | |
(?=exp) | 零宽度正预测先行断言:断言exp一定在匹配的右边出现,也就是说断言后面一定跟个exp |
(?<=exp) | 零宽度正回顾:后发断言:断言exp一定出现在匹配的左边,业绩是前面一定有个exp前缀 |
负向零宽断言 | |
(?!exp) | 零宽度付预测先行断言:断言一定不会在右侧 |
(?<!exp) | 零宽度负回顾后发断言:断言一定不能出现在左侧 |
注释 | |
(?#comment) | 注释 |
总结:分组和捕获是同一个意思;同时在正则中能用简单表达式就不要用复杂的
贪婪与非贪婪
正则表达杀死默认是贪婪模式,尽量多匹配更长的字符串。非贪婪模式很简单就是再重复的符号后加一个”?”,匹配最少的字符串。
代码 | 说明 |
---|---|
*? | 匹配人一次,尽量少重复 |
+? | 匹配至少一次,少重复 |
?? | 匹配0或依次,少重复 |
{n,}? | 匹配至少n次,少重复 |
{n,m}? | 匹配n到m次,,少重复 |
- 引擎选项
代码 | 说明 | Python |
---|---|---|
IgnoreCase | 匹配时忽略大小写 | re.I,re.IGNORECASE |
Singleline | 单行模式:.可以匹配所有字符,包括\n | re.S,re.DOTALL |
Multiline | 多行模式:^行首,$行尾 | re.M,re.MULTILINE |
IgnorePatternWhitespace | 忽略表达式中的空白字符,如果要使用空白字符用转义,#可以用来做注释 | re.X,re.VERBOSE |
练习
- 匹配0-999之间的数字 #匹配两位数 [1]?\d #匹配三位数 ^([1-9]\d\d?|\d)(?!\w+) ^([1-9])\d\d?|\d)\r?$
- 匹配点分四段式IP
关于IP地址验证问题: - 可以把数据提取出来后交给IP地址解析库(socket模块)处理,解析异常就说明IP有问题,如果正常则表示Ip合法 import socket nw = socket.inet_aton('1.2.4.8') # 输入IP字符串,返回bytes,如果抛出异常则表示IP不合法 print(nw,socket.inet_ntoa(nw)) # inet_ntoa是反向解析 - 可以使用复杂正则表达式验证地址正确性 - 前导0是可以的
(?:(25[0-5]|2[0-4]\d|[01]?\d\d?).){3}(25[0-5]|2[0-4]\d|[01]?\d\d?)
- 选出有Ftp的连接,且文件类型是gz或者xz的文件名
ftp://ftp.astron.com/pub/file/file-5.14.tar.gzftp://ftp.gmplib.org/pub/gmp-5.1.2/gmp-5.1.2.tar.xzftp://ftp.vim.org/pub/vim/unix/vim-7.3.tar.bz2http://anduin.linuxfromscratch.org/sources/LFS/lfs-packages/conglomeration//iana-etc/iana-etc-2.30.tar.bz2http://anduin.linuxfromscratch.org/sources/other/udev-lfs-205-1.tar.bz2http://download.savannah.gnu.org/releases/libpipeline/libpipeline-1.2.4.tar.gzhttp://download.savannah.gnu.org/releases/man-db/man-db-2.6.5.tar.xzhttp://download.savannah.gnu.org/releases/sysvinit/sysvinit-2.88dsf.tar.bz2http://ftp.altlinux.org/pub/people/legion/kbd/kbd-1.15.5.tar.gzhttp://mirror.hust.edu.cn/gnu/autoconf/autoconf-2.69.tar.xzhttp://mirror.hust.edu.cn/gnu/automake/automake-1.14.tar.xz
<?<=.*ftp.*/>[^/]\.(?:gz|xz)
Python的re
Python使用re模块提供了正则的处理能力
常量
- re.M = re.MULTILINE -> 多行模式
- re.S = re.DOTALL -> 单行模式
- re.I = re.IGNORECASE -> 忽略大小写
- re.X = re.VERBOSE -> 忽略表达式中的空白字符
使用‘|’位或运算符开启多种选项
方法
- 编译
- re.compile(pattern,flags=0)
- 设定flags,编译模式,返回正则表达式对象
- pattern是正则表达式字符串,flags是选项。正则表达式需要被编译,为了提高效率,这些便宜的结果被保存,下次使用就不需要再次编译。re的其他方法为提高效率都调用了编译方法。
- re.compile(pattern,flags=0)
- 单次匹配
- re.match(pattern,string,flags=0)/regex.match(string[,pos[,endpos]])match匹配从字符串的开头匹配,regex对象match方法可以设定开始位置和结束位置。返回match对象
- re.search(pattern,string,flags=0)/regex.search(string[,pos[,edpos]])从头搜索知道第一个匹配,regex对象search方法可以设定开始位置和结束位置,返回match对象
- re.fullmatch(pattern,string,flags=0)/regex.match(string[,pos[,endpos]])这个字符串和正则表达式匹配,要完全匹配,多了或少了都不行
- 以上三种方法返回的都是match对象
- 全部匹配
- re.findall(pattern,string,flags=0)等价于regex.findall(string[,pos[,endpos]])对整个字符串从左到右匹配,返回所有匹配项的列表
- re.finditer(pattern,string,flags=0)等价于regex.finditer(string[,pos[,endpos]])对整个字符串从左向右匹配,返回所有匹配项的迭代器。每次迭代返回的是match对象
- 匹配替换
- re.sub(pattern,replacement,string,count=0,flags=0)使用pattern对字符串string进行匹配,对匹配项使用repl替换。repl可以是string、bytes、function
- subn(pattern,replacement,string,count=0,flags=0)返回一个元组(new_string,number_of__subs_made)
- 分割字符串
- 字符串分割函数不能指定多个字符进行分割,不好用。
- re.split(pattern,string,maxsplit=0,flags=0)maxsplit指最大分割数,默认是全部分割
import re s = '''01 bottle 02 bag 03 big1 100 able''' print(s.split()) #做不到 ret = re.split('[\s\d]+',s) print(1,ret) regex = re.compile('^[\s\d]+') ret = regex.split(s) print(2,ret) regex = re.compile('^[\s\d]+',re.M) ret = regex.split(s) print(3,ret) regex = re.compile('\s+\d+\s+') ret = regex.split(' ' + s) print(4,ret)
- 分组使用小括号的pattern捕获的数据被放到了组group中。
- match、search函数可以返回match对象;findall返回字符串列表;finditer返回一个个match对象
- 如果pattern中使用了分组,如果有匹配的结果,会在match对象中:使用group(N)方式返回对应分组,1-n是对应的分组,0返回整个匹配的字符串;如果使用了命名分组,可以使用group(‘name’)的方式取分组;也可以使用groups()返回所有命名的分组;使用groupdict()返回所有命名的分组组成的字典
练习
- 匹配邮箱地址
test@hot-mail.com v-ip@magedu.com web.manager@magedu.com super.user@gmail.com a@w-a-com
- 匹配html标记内的内容
<a href='http://www.baidu.com/index.html' target='_blank'>马哥教育</a>
- 匹配url
http://www.baidu.com/index.html https://login.magedu.com file:///etx/syconf/newksa
- 匹配二代身份证ID
- 匹配密码强弱
- 单词统计
1.
[\w-]+@[^\s-]+.com
\w+[-.\w]*@[\w-]+(\.[\w-]+)+
2.
<a href='(?<url>[^']+)' [^<>]+>\w+</a>
html提取
<[^<>]+>(.*)<[^<>]+>
匹配标记a
<(\w+)\s+{^<>}+>(.*)(</\1>)
3.
url提取
(\w+)://([^\s]+)
4.
\d{17}[0-9xX]|\d{15}
5.
^[a-zA-Z0-9_]{10,15}$
6. 单词统计
import re
from collections import defaultdict
regex=re.compile(r'[^\w-]')
d = defaultdict(lambda : 0)
with open('d:/sample.txt',encoding='utf-8') as f:
for line in f:
for x in regex.split(line):
if len(x) >= 1:
d[x.lower()] += 1
# print(d)
print(sorted(d.items(),key= lambda x:x[1],reverse=True))
print(d['path1'],d['sub-path'])
def word_cnt(path:str):
d = defaultdict(lambda :0)
with open('d:/sample.txt',encoding='utf-8') as f:
for line in f:
for x in regex.split(line):
if len(x) >= 1:
d[x.lower()] += 1
return d
#print(word_cnt('d:/sample.txt'))
i = 0
for x in sorted(word_cnt('d:/sample.txt').items(),key = lambda x:x[1],reverse=True):
if i < 10:
print(x)
i += 1
print(word_cnt('d:/sample.txt')['path1'],word_cnt('d:/sample.txt')['sub-path'])
- 1-9 ↩
本文来自投稿,不代表Linux运维部落立场,如若转载,请注明出处:http://www.178linux.com/88237