Python正则表达式
概述
- 正则表达式,Regular Expression,缩写为regex、regexp、RE等。
- 正则表达式是文本处理极为重要的技术,用它可以对字符串按照某种规则进行检索、替换。
- 1970年代,Unix之父Ken Thompson将正则表达式引入到Unix中文本编辑器ed和grep命令中,由此正则表达式普及开来。
- 1980年后,perl语言对Henry Spencer编写的库,扩展了很多新的特性。1997年开始,Philip Hazel开发出了PCRE (Perl Compatible Regular Expressions),它被PHP和HTTPD等工具采用。
- 正则表达式应用极其广泛,shell中处理文本的命令、各种高级编程语言都支持正则表达式。
分类
BRE
- 基本正则表达式,
grep、sed、vi
等软件支持。vim
有扩展。
ERE
- 扩展正则表达式,
egrep(grep -E)、sed -r
等。
PCRE
- 几乎所有高级语言都是
PCRE
的方言或者变种。Python从1.6开始使用SRE正则表达式引擎,可以认为是PCRE
的子集,见模块re
。
基础语法
元字符 metacharacter
正则表达式 |
说明 |
示例 |
. |
匹配除换行符外的任意一个字符 |
a.b 匹配 “axb”, “aab”, 但不匹配 “a\nb” |
[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” |
\d |
匹配一个数字 |
\d 匹配 “123” 中的 ‘1’ |
\D |
匹配一个非数字 |
\D 匹配 “abc” 中的 ‘a’ |
\s |
匹配一个空白字符 |
\s 匹配空格、制表符等 |
\S |
匹配一个非空白字符 |
\S 匹配 “word” 中的 ‘w’ |
\w |
匹配一个单词字符 |
\w 匹配 “word” 中的 ‘w’ |
\W |
匹配一个非单词字符 |
\W 匹配 “123” 中的 ‘1’ |
转义
凡是在正则表达式中有特殊意义的符号,如果想使用它的本意,请使用\转义。 反斜杠自身,得使用\
\r、\n还是转义后代表回车、换行
重复
正则符号 |
说明 |
举例 |
* |
表示前面的正则表达式会重复0次或多次 |
e\w* 单词中e后面可以有非空白字符 |
? |
表示前面的正则表达式会重复0次或1次 |
e\w+ 单词中e后面至少有一个非空白字符 |
+ |
表示前面的正则表达式重复至少1次 |
e\w? 单词中e后面至多有一个非空白字符 |
{n} |
重复固定的n次 |
e\w{1} 单词中e后面只能有一个非空白字符 |
{n,} |
重复至少n次 |
e\w{1,} 等价 e\w+ e\w{0,} 等价 e\w* e\w{0,1} 等价 e\w? |
{n,m} |
重复n到m次 |
e\w{1,10} 单词中e后面至少1个,至多10个非空 白字符 |
或
代码 |
说明 |
举例 |
x|y |
匹配x或者y |
wood took foot food使用 w|food 或者 (w|f)ood |
捕获
代码 |
说明 |
举例 |
(pattern) |
使用小括号指定一个子表达式,也叫分组 捕获后会自动分配组号从1开始 |
可以改变优先级 | |
| \数字
| 匹配对应的分组 | (very) \1 匹配very very,但捕获的组group是very |
| (?:pattern)
| 如果仅仅为了改变优先级,就不需要捕获分组 | (?:w|f)ood ‘industr(?:y|ies)’ 等价 ‘industry|industries’ |
| (?<name>exp)``(?'name'exp)
| 命名分组捕获,但是可以通过name访问分组。Python语法必须是(?P<name>exp)
| |
断言
零宽断言
- 测试字符串为wood took foot food
代码 |
说明 |
举例 |
(?=exp) |
零宽度正预测先行断言 |
f(?=oo) f后面一定有oo出现 |
(?<=exp) |
零宽度正回顾后发断言 |
(?<=f)ood 、(?<=t)ook 分别匹配 ood 、ook ,ook 前一定有 t 出现 |
负向零宽断言
代码 |
说明 |
举例 |
(?!exp) |
零宽度负预测先行断言断言exp一定不会出现在右侧,也就是说断言后面一定是exp |
\d{3}(?!\d) 匹配3数字,断言3位数字后面一定不能是数字 foo(?!d) foo后面一定不是d |
(?<!exp) |
零宽度负回顾后发断言 断言exp一定不能出现在左侧,也就是说断言前面一定不能是exp |
(?<!f)ood ood的左边一定不是f |
代码 |
说明 |
举例 |
(?#comment) |
注释 |
f(?=oo)(?#这个后断言不捕获) |
贪婪与非贪婪
默认是贪婪模式,也就是说尽量多匹配更长的字符串。
非贪婪很简单,在重复的符号后面加上一个?问号,就尽量的少匹配了。
代码 |
说明 |
举例 |
*? |
匹配任意次,但尽可能少重复 |
|
+? |
匹配至少1次,但尽可能少重复 |
|
?? |
匹配0次或1次,但尽可能少重复 |
|
{n,}? |
匹配至少n次,但尽可能少重复 |
|
{n,m}? |
匹配至少n次,至多m次,但尽可能少重复 |
very very happy 使用v.*y和v.*?y
引擎选项
代码 |
说明 |
Python |
IgnoreCase |
匹配时忽略大小写 |
re.I
re.IGNORECASE |
Singleline |
单行模式,. 可以匹配所有字符,包括\n |
re.S
re.DOTALL |
Multiline |
多行模式,^ 行首、$ 行尾 |
re.M
re.MULTILINE |
IgnorePatternWhitespace |
忽略表达式中的空白字符,如果要使用空白字符用转义,# 可以用来做注释 |
re.X
re.VERBOSE |
单行模式:
. 可以匹配所有字符,包括换行符
^ 表示整个字符串的开头,$整个字符串的结尾
多行模式:
. 可以匹配除了换行符之外的字符,多行不影响.点号
^ 表示行首,$行尾,只不过这里的行是每一个行
默认模式:可以看做待匹配的文本是一行,不能看做多行,.点号不能匹配换行符,^和$表示行首和行
尾,而行首行尾就是整个字符串的开头和结尾
单行模式:基本和默认模式一样,只是.点号终于可以匹配任意一个字符包括换行符,这时所有文本就是
一个长长的只有一行的字符串。^就是这一行字符串的行首,$就是这一行的行尾。
多行模式:重新定义了行的概念,但不影响.点号的行为,^和$还是行首行尾的意思,只不过因为多行模
式可以识别换行符了。”开始”指的是\n后紧接着下一个字符;”结束”指的是\n前的字符,注意最后一行
结尾可以没有\n
简单讲,单行模式只影响.点号行为,多行模式重新定义行影响了^和$
注意:注意字符串中看不见的换行符,\r\n会影响e$的测试,e$只能匹配e\n
- 举例
very very happy
my primary key
上面2行happy
之后,有可能是\r\n
结尾。
y$
单行匹配key的y
,多行匹配happy和key的y
。
.$指的是此行的结尾,而默认模式和单行模式都是一行,指的是这个大字符串的最后一个字符,就是key的y
。
正则练习参考
IP地址
| 192.168.1.150 |
| 0.0.0.0 |
| 255.255.255.255 |
| 17.16.52.100 |
| 172.16.0.100 |
| 400.400.999.888 |
| 001.022.003.000 |
| 257.257.255.256 |
| |
| (\d{1,3}\.){3}\d{1,3} |
| (?:\d{1,3}\.){3}\d{1,3} |
- 对于ip地址验证的问题
- 可以把数据提出来后,交给IP地址解析库 处理,如果解析异常,就说明有问题,正则的验证只是一个初步的筛选,把明显错误过滤掉。
- 可以使用复杂的正则表达式验证地址正确性
- 前导0是可以的
| import socket |
| nw = socket.inet_aton('192.168.05.001') |
| |
| 200是特殊的,要再单独分情况处理 |
| 25[0-5]|2[0-4]\d|[01]?\d\d? 这就是每一段的逻辑 (?:(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.gz |
| ftp://ftp.gmplib.org/pub/gmp-5.1.2/gmp-5.1.2.tar.xz |
| ftp://ftp.vim.org/pub/vim/unix/vim-7.3.tar.bz2 |
| http://anduin.linuxfromscratch.org/sources/LFS/lfs- |
| packages/conglomeration//iana-etc/iana-etc-2.30.tar.bz2 |
| http://anduin.linuxfromscratch.org/sources/other/udev-lfs-205-1.tar.bz2 |
| http://download.savannah.gnu.org/releases/libpipeline/libpipeline- |
| 1.2.4.tar.gz |
| http://download.savannah.gnu.org/releases/man-db/man-db-2.6.5.tar.xz |
| http://download.savannah.gnu.org/releases/sysvinit/sysvinit-2.88dsf.tar.bz2 |
| http://ftp.altlinux.org/pub/people/legion/kbd/kbd-1.15.5.tar.gz |
| http://mirror.hust.edu.cn/gnu/autoconf/autoconf-2.69.tar.xz |
| http://mirror.hust.edu.cn/gnu/automake/automake-1.14.tar.xz |
| |
| .*ftp.*\.(?:gz|xz) |
| .*ftp.*/([^/]+(?:.xz|.gz))$ |
匹配邮箱地址
| test@hot-mail.com |
| v-ip@asjin.com |
| web.manager@asjin.com.cn |
| super.user@google.com |
| a@w-a-com |
- 确定规则,邮箱名为字母、数字、下划线、减号、点号,域名一样
| \w[\w.-]*@([\w-]+\.)+\w+ |
| @前至少一个字符开头,可以有字母、数字、下划线、减号、点号 |
| @后至少有一个字符后跟点,重复至少1次,最后以1个字符串结尾 |
匹配html标记
| <a href='http://www.asjin.com/index.html' target='_blank'>逝水锦遥</a> |
- 有的时候可以这样写
<a href=http://www.asjin.com/index.html target='_blank'>
逝水锦遥 </a>
属性attr不标准的写法不写单双引号,这个一般交给浏览器可以正常处理。
| <a href='http://www.asjin.com/index.html' target='_blank'>逝水锦遥</a><a>逝水 锦遥1</a> |
| |
| >(\w+)< |
| <a.*?>[^<>]* |
a标签要到第一个右尖括号结束,不能贪婪。内容是其之后第一个左尖括号之前
匹配URL
| http://www.asjin.com/index.html |
| https://login.asjin.com |
| file:///ect/sysconfig/network |
| (\w+)://(\S*) |
匹配二代中国身份证ID
| 321105700101003 |
| 321105197001010030 |
| 11210020170101054X |
| 17位数字+1位校验码组成 前6位地址码,8位出生年月,3位数字,1位校验位(0-9或X) |
| 身份证验证 身份证验证需要使用公式计算,最严格的应该实名验证。 \d{17}[0-9xX]|\d{15} |
python正则使用
- Python使用re模块提供了正则表达式处理的能力.
常用
常量 |
说明 |
re.M re.MULTILINE |
多行模式 |
re.I re.IGNORECASE |
忽略大小写 |
re.S re.DOTALL |
单行模式 |
re.X re.VERBOSE |
忽略表达式中的空白字符 |
| import re |
| re.M | re.S |
| regex = re.compile('\d') |
方法、编译
re.compile(pattern, flags=0)
设定flags,编译模式,返回正则表达式对象regex。
pattern就是正则表达式字符串,flags是选项。正则表达式需要被编译,为了提高效率,这些编译后的 结果被保存,下次使用同样的pattern的时候,就不需要再次编译。
re的其它方法为了提高效率都调用了编译方法,就是为了提速。
单次匹配
re.match(pattern, string, flags=0)
regex.match(string[, pos[, endpos]])
- match匹配从字符串的开头匹配,regex对象match方法可以重设定开始位置和结束位置。返回match对象(默认必须从0处匹配上,或指定位置开始匹配)
re.search(pattern, string, flags=0)
regex.search(string[, pos[, endpos]])
- 从头搜索直到第一个匹配,regex对象search方法可以重设定开始位置和结束位置,返回match对象(search 在字符串中搜索,从0或者指定位置开始向后搜索)
re.fullmatch(pattern, string, flags=0)
regex.fullmatch(string[, pos[, endpos]])
| import re |
| |
| s = '''bottle\nbag\nbig\napple''' |
| |
| for i, c in enumerate(s ,1): |
| print((i-1,c), end='\n' if i % 5 == 0 else '\t') |
| print() |
| |
| m = re.match('^a',s, re.M) |
| print(type(m),m) |
| |
| |
| |
| regex = re.compile('^a',re.M) |
| m = regex.match(s,15,16) |
| print(type(m),m) |
| |
| |
| regex = re.compile('^b',re.M) |
| m = regex.match(s,1) |
| print(type(m),m) |
| |
| |
| |
| |
| |
| m = re.search('^a',s) |
| print(type(m),m) |
| |
| regex = re.compile('^b',re.M) |
| m = regex.search(s,1) |
| print(type(m),m) |
| |
| |
| m = re.fullmatch('b.*', s , re.M | re.S ) |
| print(type(m),m) |
全文搜索
re.findall(pattern, string, flags=0)
regex.findall(string[, pos[, endpos]])
对整个字符串,从左至右匹配,返回所有匹配项的列表(findall 在文本中,全文搜索、匹配多次、返回列表,元素是字符串)
re.finditer(pattern, string, flags=0)
regex.finditer(string[, pos[, endpos]])
对整个字符串,从左至右匹配,返回所有匹配项,返回迭代器。 注意每次迭代返回的是match对象。(finditer 全文搜索、但是返回迭代器,元素是Match对象)
| import re |
| s = '''bottle\nbag\nbig\nable''' |
| for i,c in enumerate(s, 1): |
| print((i-1, c), end='\n' if i%10==0 else ' ') |
| print() |
| (0, 'b') (1, 'o') (2, 't') (3, 't') (4, 'l') (5, 'e') (6, '\n') (7, 'b') (8, |
| 'a') (9, 'g') |
| (10, '\n') (11, 'b') (12, 'i') (13, 'g') (14, '\n') (15, 'a') (16, 'b') (17, |
| 'l') (18, 'e') |
| |
| result = re.findall('b', s) print(1, result) |
| regex = re.compile('^b') |
| result = regex.findall(s) print(2, result) |
| regex = re.compile('^b', re.M) result = regex.findall(s, 7) print(3, result) |
| regex = re.compile('^b', re.S) result = regex.findall(s) print(4, result) |
| regex = re.compile('^b', re.M) result = regex.findall(s, 7, 10) print(5, result) |
| |
| regex = re.compile('^b\w+', re.M) |
| result = regex.finditer(s) |
| print(type(result)) |
| r = next(result) |
| print(type(r), r) |
| print(r.start(), r.end(), s[r.start():r.end()]) r = next(result) |
| print(type(r), r) |
| print(r.start(), r.end(), s[r.start():r.end()]) |
匹配替换
re.sub(pattern, replacement, string, count=0, flags=0)
regex.sub(replacement, string, count=0)
使用pattern对字符串string进行匹配,对匹配项使用repl替换。
replacement可以是string、bytes、function
re.subn(pattern, replacement, string, count=0, flags=0)
regex.subn(replacement, string, count=0)
| import re |
| s = '''bottle\nbag\nbig\napple''' |
| for i,c in enumerate(s, 1): |
| print((i-1, c), end='\n' if i%8==0 else ' ') |
| print() |
| (0, 'b') (1, 'o') (2, 't') (3, 't') (4, 'l') (5, 'e') (6, '\n') (7, 'b') (8, |
| 'a') (9, 'g') |
| (10, '\n')(11, 'b')(12, 'i')(13, 'g')(14, '\n')(15, 'a')(16, 'p')(17, 'p') |
| (18, 'l')(19, 'e') |
| |
| regex = re.compile('b\wg') |
| result = regex.sub('asjin', s) |
| print(1, result) |
| result = regex.sub('asjin', s, 1) |
| regex = re.compile('\s+') |
| result = regex.subn('\t', s) |
| print(3, result) |
分组
使用小括号的pattern捕获的数据被放到了组group中。 match、search函数可以返回match对象;findall返回字符串列表;finditer返回一个个match对象
- 如果pattern中使用了分组,如果有匹配的结果,会在match对象中
- 使用group(N)方式返回对应分组,1到N是对应的分组,0返回整个匹配的字符串,N不写缺省为0 2. 如果使用了命名分组,可以使用group(‘name’)的方式取分组
- 也可以使用groups()返回所有组
- 使用groupdict() 返回所有命名的分组
| import re |
| s = '''bottle\nbag\nbig\napple''' |
| for i,c in enumerate(s, 1): |
| print((i-1, c), end='\n' if i%10==0 else ' ') |
| print() |
| |
| regex = re.compile('(b\w+)') |
| result = regex.match(s) |
| print(type(result)) |
| print(1, 'match', result.group(), result.group(0), result[0], result.groups()) |
| result = regex.search(s, 1) |
| |
| regex = re.compile('(b\w+)\n(?P<name2>b\w+)\n(?P<name3>b\w+)') result = regex.match(s) |
| print(3, 'match', result) |
| print(4, result.group(3), result.group(2), result.group(1)) print(5, result.group(0).encode()) |
| print(6, result.group('name2'), result.group('name3')) |
| print(6, result.groups()) |
| print(7, result.groupdict()) |
| result = regex.findall(s) |
| print(type(x), x) |
| regex = re.compile('(?P<head>b\w+)') |
| result = regex.finditer(s) |
| for x in result: |
| print(type(x), x, x.group(), x.group('head'), x['head'], x[0]) |
如果有分组,findall返回的是分组的内容,而不是match匹配的字符串。
有没有分组,都可以使用Match对象的group(0),它总是为匹配的字符串。
分割字符串
字符串的分割函数split,太难用,不能指定多个字符进行分割。
re.split(pattern, string, maxsplit=0, flags=0)
re.split
分割字符串
| import re |
| s = """ |
| os.path.abspath(path) |
| normpath(join(os.getcwd(), path)). |
| """ |
| |
| print(s.split()) |
| print(re.split('[\.()\s,]+', s)) |