抱歉,您的浏览器无法访问本站
本页面需要浏览器支持(启用)JavaScript
了解详情 >

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中处理文本的命令、各种高级编程语言都支持正则表达式。

分类

  1. BRE
    1. 基本正则表达式,grep、sed、vi等软件支持。vim有扩展。
  2. ERE
    1. 扩展正则表达式,egrep(grep -E)、sed -r等。
  3. PCRE
    1. 几乎所有高级语言都是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 分别匹配 oodookook 前一定有 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地址

  • 匹配合法的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} # 400.400.999.888
  • 对于ip地址验证的问题
    • 可以把数据提出来后,交给IP地址解析库 处理,如果解析异常,就说明有问题,正则的验证只是一个初步的筛选,把明显错误过滤掉。
    • 可以使用复杂的正则表达式验证地址正确性
    • 前导0是可以的
import socket
nw = socket.inet_aton('192.168.05.001') # 错了抛异常 print(nw, socket.inet_ntoa(nw))
# 分析: 每一段上可以写的数字有1、01、001、000、23、023、230、100,也就说1位就是0-9,2位每一位也是 0-9,3位第一位只能0-2,其余2位都可以0-9 (?:([0-2]\d{2}|\d{1,2})\.){3}([0-2]\d{2}|\d{1,2}) # 解决超出200的问题,但是256 呢? 
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))$ # 后面的分组就是文件名 (?<=.*ftp.*/)[^/]*\.(?:gz|xz) # 断言文件名前一定还有ftp,python的re模块不支持断言中 使用*、+或{3,4}等不固定长度的匹配

匹配邮箱地址

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标记

  • 提取href中的链接url,提取文字“逝水锦遥”
<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) # mach 只匹配单次
print(type(m),m)

# 打印结果 <class 'NoneType'> None

regex = re.compile('^a',re.M) # compile 编译正则表达式,可以重复使用
m = regex.match(s,15,16)
print(type(m),m)    
# 打印结果 <class 'NoneType'> None

regex = re.compile('^b',re.M) # compile 编译正则表达式,可以重复使用
m = regex.match(s,1)
print(type(m),m)

# 因此,match 从头开始匹配,如果匹配不到,返回None,如果匹配到了,返回一个match对象,match对象有group方法,可以打印出匹配到的内容

# 因此 search 在字符串中搜索,从0或者指定位置开始向后搜索 
# 因此 re.fullmatch 要求全长匹配,一个不落(如果指定子串,子串要全匹配)
m = re.search('^a',s) # search 匹配多次
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')
# findall方法
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) # bag big
regex = re.compile('^b', re.S) result = regex.findall(s) print(4, result) # bottle
regex = re.compile('^b', re.M) result = regex.findall(s, 7, 10) print(5, result) # bag
# finditer方法
regex = re.compile('^b\w+', re.M)
result = regex.finditer(s)
print(type(result))
r = next(result)
print(type(r), r) # Match对象
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) # 替换1次 print(2, result) # 被替换后的字符串
regex = re.compile('\s+')
result = regex.subn('\t', s)
print(3, result) # 被替换后的字符串及替换次数的元组

分组

使用小括号的pattern捕获的数据被放到了组group中。 match、search函数可以返回match对象;findall返回字符串列表;finditer返回一个个match对象

  1. 如果pattern中使用了分组,如果有匹配的结果,会在match对象中
    1. 使用group(N)方式返回对应分组,1到N是对应的分组,0返回整个匹配的字符串,N不写缺省为0 2. 如果使用了命名分组,可以使用group(‘name’)的方式取分组
    2. 也可以使用groups()返回所有组
    3. 使用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) # 从指定位置向后匹配一次 print(2, 'search', result.groups()) #
# 命名分组
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()) # 0 返回整个匹配字符串,即match
print(6, result.group('name2'), result.group('name3'))
print(6, result.groups())
print(7, result.groupdict())
result = regex.findall(s) # 返回什么,有几项? for x in result: # 有分组里面放的东西不一样
    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()) # 做不到['os.path.abspath(path)', 'normpath(join(os.getcwd(),', 'path)).'] 
print(re.split('[\.()\s,]+', s))

评论