1. 正则表达式
1.1 简介
正则表达式(英语:Regular Expression,常简写为regex、regexp或RE),又称正则表示式、正则表示法、规则表达式、常规表示法,是计算机科学的一个概念。正则表达式使用单个字符串来描述、匹配一系列匹配某个句法规则的字符串。在很多文本编辑器里,正则表达式通常被用来检索、替换那些匹配某个模式的文本。
1.2 用途
给定一个正则表达式和另一个字符串,我们可以达到如下的目的:
- 给定的字符串是否符合正则表达式的过滤逻辑(称作“匹配”):
- 可以通过正则表达式,从字符串中获取我们想要的特定部分。
1.3 特点
由于正则表达式主要应用对象是文本,因此它在各种文本编辑器场合都有应用,小到著名编辑器EditPlus,大到Microsoft Word、Visual Studio等大型编辑器,都可以使用正则表达式来处理文本内容。
- 灵活性、逻辑性和功能性非常强。
- 可以迅速地用极简单的方式达到字符串的复杂控制。
- 对于刚接触的人来说,比较晦涩难懂。
1.4 基本语法
一个正则表达式通常被称为一个模式(pattern),为用来描述或者匹配一系列匹配某个句法规则的字符串。
一个正则表达式通常由基本的元字符+限定符构成:
例如:Handel、Händel和Haendel这三个字符串,都可以由H(a|ä|ae)ndel这个模式来描述。
这个式子的意思是:
匹配H一次,a、ä或者ae一次,n一次,d一次,e一次,l一次。
1.4.1 元字符
元字符 | 描述 | 实例 |
---|---|---|
. | 匹配除换行符以外的任何字符。使用"\."可匹配一个"."如果放在"[]"内也表示匹配一个"."而不是"除换行符以外的任何字符"。 | |
() | 将一系列字符组合为一个group,当成功匹配到字符时可分别取出它们。 | |
f | 匹配一个换页符。等价于 x0c 和 cL。 | |
n | 匹配一个换行符。等价于 x0a 和 cJ。 | |
r | 匹配一个回车符。等价于 x0d 和 cM。 | |
[...] | 当字符括在中括号内时,与在任何别的位置一样,普通字符在中括号内表示其本身,即,它在输入文本中匹配一次其本身。大多数特殊字符在中括号表达式内出现时失去它们的意义。但除外,要匹配 字符,请使用\\。 | |
1 | 匹配"[]"不包含的任意字符。 | |
w | 匹配字母、数字、下划线。等价于'[A-Za-z0-9_]'。 | |
W | 匹配非字母、数字、下划线。等价于'[ ^A-Za-z0-9_]'。 | |
s | 匹配任何空白字符,包括空格、制表符、换页符等等。等价于 [ fnrtv]。 | |
S | 匹配任何非空白字符。等价于 [ ^fnrtv]。 | |
d | 匹配一个数字字符。等价于 [0-9]。 | |
D | 匹配一个非数字字符。等价于 2。 | |
t | 匹配一个制表符。等价于 x09 和 cI。 | |
v | 匹配一个垂直制表符。等价于 x0b 和 cK。 |
1.4.2 限定符
字符后面不加匹配次数表示匹配前面的字符仅一次。
字符 | 作用 |
---|---|
? | 表示匹配前一个元素出现零次或一次。例如,colou?r可以匹配“colour”和“color”。 |
* | 表示匹配前一个元素出现零次或多次。例如,ab*c可以匹配"ac", "abc", "abbc", "abbbc"等等。 |
+ | 表示匹配前一个元素出现零次或一次(至少一次)。 例如: ab+c可以匹配"abc", "abbc", "abbbc"等等, 但是匹配不到 "ac"。 |
{n} | 表示匹配前一个元素出现n次。 |
{min,} | 表示匹配前一个元素出现至少min次。 |
{,max} | 表示匹配前一个元素出现最多max次。 |
{min,max} | 表示匹配前一个元素出现至少min次,最多max次。 |
1.4.3 定位符
字符 | 描述 |
---|---|
^ | 匹配输入字符串开始的位置。 |
$ | 匹配输入字符串结尾的位置。 |
b | 匹配一个单词边界,即字与空格间的位置。'erb' 可以匹配"never" 中的 'er',但不能匹配 "verb" 中的 'er'。 |
B | 非单词边界匹配。'erB' 能匹配 "verb" 中的 'er',但不能匹配 "never" 中的 'er'。 |
1.4.4 贪婪
正则表达式默认匹配模式是贪婪模式(尽量多匹配)如需要非贪婪匹配(最小匹配)可以使用?标记*, +, ? 或{M,N}为非贪婪匹配模式。
如:使用(l.+?o)匹配"Hello World":
如果+后不加?表示这个+是贪婪的,那么匹配结果就是"llo Wo"。
如果加了?,就按最大符合的字符匹配,匹配结果则是 "llo" 。
1.5 优先级
优先级 | 符号 | |
---|---|---|
最高 | \ | |
高 | ()、(?:)、(?=)、[] | |
中 | *、+、?、{n}、{n,}、{n,m} | |
低 | ^、$、元字符、任何字符 | |
次最低 | 串接,即相邻字符连接在一起 | |
最低 | \ |
2. Java与正则表达式
JDK 中的 java.util.regex
包提供了对正则表达式的支持。
java.util.regex
有三个核心类:
- Pattern 类:
Pattern
是一个正则表达式的编译表示。 - Matcher 类:
Matcher
是对输入字符串进行解释和匹配操作的引擎。 - PatternSyntaxException:
PatternSyntaxException
是一个非强制异常类,它表示一个正则表达式模式中的语法错误。
2.1 Pattern 类
Pattern类没有公共构造方法。要创建一个Pattern对象,你必须首先调用其静态方法compile,加载正则规则字符串,然后返回一个 Pattern 对象。
与Pattern类一样,Matcher类也没有公共构造方法。你需要调用Pattern对象的matcher方法来获得一个Matcher对象。
【示例】Pattern 和 Matcher 的初始化
Pattern p = Pattern.compile(regex);
Matcher m = p.matcher(content);
2.2 Matcher 类
Matcher 类可以说是 java.util.regex 中的核心类,它有三类功能:校验、查找、替换。
2.2.1 校验
为了校验文本是否与正则规则匹配,Matcher 提供了以下几个返回值为 boolean
的方法。
序号 | 方法及说明 |
---|---|
1 | public boolean lookingAt() 尝试将从区域开头开始的输入序列与该模式匹配。 |
2 | public boolean find() 尝试查找与该模式匹配的输入序列的下一个子序列。 |
3 | public boolean find(int start)重置此匹配器,然后尝试查找匹配该模式、从指定索引开始的输入序列的下一个子序列。 |
4 | public boolean matches() 尝试将整个区域与模式匹配。 |
如果你傻傻分不清上面的查找方法有什么区别,那么下面一个例子就可以让你秒懂。
【示例】lookingAt、find、matches
public static void main(String[] args) {
checkLookingAt("hello", "helloworld");
checkLookingAt("world", "helloworld");
checkFind("hello", "helloworld");
checkFind("world", "helloworld");
checkMatches("hello", "helloworld");
checkMatches("world", "helloworld");
checkMatches("helloworld", "helloworld");
}
private static void checkLookingAt(String regex, String content) {
Pattern p = Pattern.compile(regex);
Matcher m = p.matcher(content);
if (m.lookingAt()) {
System.out.println(content + "\tlookingAt: " + regex);
} else {
System.out.println(content + "\tnot lookingAt: " + regex);
}
}
private static void checkFind(String regex, String content) {
Pattern p = Pattern.compile(regex);
Matcher m = p.matcher(content);
if (m.find()) {
System.out.println(content + "\tfind: " + regex);
} else {
System.out.println(content + "\tnot find: " + regex);
}
}
private static void checkMatches(String regex, String content) {
Pattern p = Pattern.compile(regex);
Matcher m = p.matcher(content);
if (m.matches()) {
System.out.println(content + "\tmatches: " + regex);
} else {
System.out.println(content + "\tnot matches: " + regex);
}
}
输出:
helloworld lookingAt: hello
helloworld not lookingAt: world
helloworld find: hello
helloworld find: world
helloworld not matches: hello
helloworld not matches: world
helloworld matches: helloworld
说明
regex = "world"
表示的正则规则是以 world 开头的字符串,regex = "hello"
和 regex = "helloworld"
也是同理。
lookingAt
方法从头部开始,检查 content 字符串是否有子字符串于正则规则匹配。find
方法检查 content 字符串是否有子字符串于正则规则匹配,不管字符串所在位置。matches
方法检查 content 字符串整体是否与正则规则匹配。
2.2.2 查找
为了查找文本匹配正则规则的位置,Matcher
提供了以下方法:
序号 | 方法及说明 |
---|---|
1 | public int start() 返回以前匹配的初始索引。 |
2 | public int start(int group) 返回在以前的匹配操作期间,由给定组所捕获的子序列的初始索引 |
3 | public int end()返回最后匹配字符之后的偏移量。 |
4 | public int end(int group)返回在以前的匹配操作期间,由给定组所捕获子序列的最后字符之后的偏移量。 |
5 | public String group()返回前一个符合匹配条件的子序列。 |
6 | public String group(int group)返回指定的符合匹配条件的子序列。 |
【示例】使用 start()、end()、group() 查找所有匹配正则条件的子序列
public static void main(String[] args) {
final String regex = "world";
final String content = "helloworld helloworld";
Pattern p = Pattern.compile(regex);
Matcher m = p.matcher(content);
System.out.println("content: " + content);
int i = 0;
while (m.find()) {
i++;
System.out.println("[" + i + "th] found");
System.out.print("start: " + m.start() + ", ");
System.out.print("end: " + m.end() + ", ");
System.out.print("group: " + m.group() + "\n");
}
}
输出
content: helloworld helloworld
[1th] found
start: 5, end: 10, group: world
[2th] found
start: 16, end: 21, group: world
2.2.3 替换
替换方法是替换输入字符串里文本的方法:
序号 | 方法及说明 |
---|---|
1 | public Matcher appendReplacement(StringBuffer sb, String replacement)实现非终端添加和替换步骤。 |
2 | public StringBuffer appendTail(StringBuffer sb)实现终端添加和替换步骤。 |
3 | public String replaceAll(String replacement) 替换模式与给定替换字符串相匹配的输入序列的每个子序列。 |
4 | public String replaceFirst(String replacement) 替换模式与给定替换字符串匹配的输入序列的第一个子序列。 |
5 | public static String quoteReplacement(String s)返回指定字符串的字面替换字符串。这个方法返回一个字符串,就像传递给 Matcher 类的 appendReplacement 方法一个字面字符串一样工作。 |
【示例】replaceFirst 和 replaceAll
public static void main(String[] args) {
String regex = "can";
String replace = "can not";
String content = "I can because I think I can.";
Pattern p = Pattern.compile(regex);
Matcher m = p.matcher(content);
System.out.println("content: " + content);
System.out.println("replaceFirst: " + m.replaceFirst(replace));
System.out.println("replaceAll: " + m.replaceAll(replace));
}
输出
content: I can because I think I can.
replaceFirst: I can not because I think I can.
replaceAll: I can not because I think I can not.
说明
replaceFirst:替换第一个匹配正则规则的子序列。
replaceAll:替换所有匹配正则规则的子序列。
【示例】appendReplacement、appendTail 和 replaceAll
public static void main(String[] args) {
String regex = "can";
String replace = "can not";
String content = "I can because I think I can.";
StringBuffer sb = new StringBuffer();
StringBuffer sb2 = new StringBuffer();
System.out.println("content: " + content);
Pattern p = Pattern.compile(regex);
Matcher m = p.matcher(content);
while (m.find()) {
m.appendReplacement(sb, replace);
}
System.out.println("appendReplacement: " + sb);
m.appendTail(sb);
System.out.println("appendTail: " + sb);
}
输出
content: I can because I think I can.
appendReplacement: I can not because I think I can not
appendTail: I can not because I think I can not.
说明
从输出结果可以看出,appendReplacement
和appendTail
方法组合起来用,功能和replaceAll
是一样的。
如果你查看replaceAll
的源码,会发现其内部就是使用appendReplacement
和appendTail
方法组合来实现的。
【示例】quoteReplacement 和 replaceAll,解决特殊字符替换问题
public static void main(String[] args) {
String regex = "\\$\\{.*?\\}";
String replace = "${product}";
String content = "product is ${productName}.";
Pattern p = Pattern.compile(regex);
Matcher m = p.matcher(content);
String replaceAll = m.replaceAll(replace);
System.out.println("content: " + content);
System.out.println("replaceAll: " + replaceAll);
}
输出
Exception in thread "main" java.lang.IllegalArgumentException: No group with name {product}
at java.util.regex.Matcher.appendReplacement(Matcher.java:849)
at java.util.regex.Matcher.replaceAll(Matcher.java:955)
at org.zp.notes.javase.regex.RegexDemo.wrongMethod(RegexDemo.java:42)
at org.zp.notes.javase.regex.RegexDemo.main(RegexDemo.java:18)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:498)
at com.intellij.rt.execution.application.AppMain.main(AppMain.java:147)
说明
String regex = "\\$\\{.*?\\}";
表示匹配类似${name}
这样的字符串。由于$
、{
、}
都是特殊字符,需要用反义字符\
来修饰才能被当做一个字符串字符来处理。
上面的例子是想将 ${productName}
替换为 ${product}
,然而replaceAll
方法却将传入的字符串中的$
当做特殊字符来处理了。结果产生异常。
如何解决这个问题?
JDK1.5 引入了quoteReplacement
方法。它可以用来转换特殊字符。其实源码非常简单,就是判断字符串中如果有\
或$
,就为它加一个转义字符\
我们对上面的代码略作调整:
m.replaceAll(replace)
改为m.replaceAll(Matcher.quoteReplacement(replace))
,新代码如下:
public static void main(String[] args) {
String regex = "\\$\\{.*?\\}";
String replace = "${product}";
String content = "product is ${productName}.";
Pattern p = Pattern.compile(regex);
Matcher m = p.matcher(content);
String replaceAll = m.replaceAll(Matcher.quoteReplacement(replace));
System.out.println("content: " + content);
System.out.println("replaceAll: " + replaceAll);
}
输出
content: product is ${productName}.
replaceAll: product is ${product}.
说明
字符串中如果有\
或$
,不能被正常解析的问题解决。
3. Python与正则表达式
正则表达式是一个特殊的字符序列,它能帮助你方便的检查一个字符串是否与某种模式匹配。
Python 自1.5版本起增加了re 模块,它提供 Perl 风格的正则表达式模式。
re 模块使 Python 语言拥有全部的正则表达式功能。
compile 函数根据一个模式字符串和可选的标志参数生成一个正则表达式对象。该对象拥有一系列方法用于正则表达式匹配和替换。
re 模块也提供了与这些方法功能完全一致的函数,这些函数使用一个模式字符串做为它们的第一个参数。
本章节主要介绍Python中常用的正则表达式处理函数。
3.1 re.match函数
re.match 尝试从字符串的起始位置匹配一个模式,如果不是起始位置匹配成功的话,match()就返回none。
函数语法:
re.match(pattern, string, flags=0)
函数参数说明:
参数 | 描述 |
---|---|
pattern | 匹配的正则表达式 |
string | 要匹配的字符串。 |
flags | 标志位,用于控制正则表达式的匹配方式,如:是否区分大小写,多行匹配等等。参见:正则表达式修饰符 - 可选标志 |
匹配成功re.match方法返回一个匹配的对象,否则返回None。
我们可以使用group(num) 或 groups() 匹配对象函数来获取匹配表达式。
匹配对象方法 | 描述 |
---|---|
group(num=0) | 匹配的整个表达式的字符串,group() 可以一次输入多个组号,在这种情况下它将返回一个包含那些组所对应值的元组。 |
groups() | 返回一个包含所有小组字符串的元组,从 1 到 所含的小组号。 |
实例
#!/usr/bin/env python3
# -*- coding:utf-8 -*-
"""
@file : regex.py
@author : Leon(fxkxb.com)
@date : October 08, 2021 15:49
@version : 1.0
@description :
"""
import re
print(re.match('www', 'www.fxkxb.com').span()) # 在起始位置匹配
print(re.match('com', 'www.fxkxb.com')) # 不在起始位置匹配
以上实例运行输出结果为:
(0, 3)
None
实例
#!/usr/bin/env python3
# -*- coding:utf-8 -*-
"""
@file : regex.py
@author : Leon(fxkxb.com)
@date : October 08, 2021 15:49
@version : 1.0
@description :
"""
import re
line = "Cats are smarter than dogs"
matchObj = re.match(r'(.*) are (.*?) .*', line, re.M | re.I)
if matchObj:
print("matchObj.group() : ", matchObj.group())
print("matchObj.group(1) : ", matchObj.group(1))
print("matchObj.group(2) : ", matchObj.group(2))
else:
print("No match!!")
以上实例执行结果如下:
matchObj.group() : Cats are smarter than dogs
matchObj.group(1) : Cats
matchObj.group(2) : smarter
3.2 re.search方法
re.search 扫描整个字符串并返回第一个成功的匹配。
函数语法:
re.search(pattern, string, flags=0)
函数参数说明:
参数 | 描述 |
---|---|
pattern | 匹配的正则表达式 |
string | 要匹配的字符串。 |
flags | 标志位,用于控制正则表达式的匹配方式,如:是否区分大小写,多行匹配等等。 |
匹配成功re.search方法返回一个匹配的对象,否则返回None。
我们可以使用group(num) 或 groups() 匹配对象函数来获取匹配表达式。
匹配对象方法 | 描述 |
---|---|
group(num=0) | 匹配的整个表达式的字符串,group() 可以一次输入多个组号,在这种情况下它将返回一个包含那些组所对应值的元组。 |
groups() | 返回一个包含所有小组字符串的元组,从 1 到 所含的小组号。 |
实例
#!/usr/bin/env python3
# -*- coding:utf-8 -*-
"""
@file : regex.py
@author : Leon(fxkxb.com)
@date : October 08, 2021 15:49
@version : 1.0
@description :
"""
import re
print(re.search('www', 'www.fxkxb.com').span()) # 在起始位置匹配
print(re.search('com', 'www.fxkxb.com').span()) # 不在起始位置匹配
以上实例运行输出结果为:
(0, 3)
(10, 13)
实例
#!/usr/bin/env python3
# -*- coding:utf-8 -*-
"""
@file : regex.py
@author : Leon(fxkxb.com)
@date : October 08, 2021 15:49
@version : 1.0
@description :
"""
import re
line = "Cats are smarter than dogs"
searchObj = re.search(r'(.*) are (.*?) .*', line, re.M | re.I)
if searchObj:
print("searchObj.group() : ", searchObj.group())
print("searchObj.group(1) : ", searchObj.group(1))
print("searchObj.group(2) : ", searchObj.group(2))
else:
print("Nothing found!!")
以上实例执行结果如下:
searchObj.group() : Cats are smarter than dogs
searchObj.group(1) : Cats
searchObj.group(2) : smarter
3.3 re.match与re.search的区别
re.match只匹配字符串的开始,如果字符串开始不符合正则表达式,则匹配失败,函数返回None;而re.search匹配整个字符串,直到找到一个匹配。
实例
#!/usr/bin/env python3
# -*- coding:utf-8 -*-
"""
@file : regex.py
@author : Leon(fxkxb.com)
@date : October 08, 2021 15:49
@version : 1.0
@description :
"""
import re
line = "Cats are smarter than dogs"
matchObj = re.match(r'dogs', line, re.M | re.I)
if matchObj:
print("match --> matchObj.group() : ", matchObj.group())
else:
print("No match!!")
matchObj = re.search(r'dogs', line, re.M | re.I)
if matchObj:
print("search --> searchObj.group() : ", matchObj.group())
else:
print("No match!!")
以上实例运行结果如下:
No match!!
search --> searchObj.group() : dogs
3.4 检索和替换
Python 的 re 模块提供了re.sub用于替换字符串中的匹配项。
语法:
re.sub(pattern, repl, string, count=0, flags=0)
参数:
- pattern : 正则中的模式字符串。
- repl : 替换的字符串,也可为一个函数。
- string : 要被查找替换的原始字符串。
- count : 模式匹配后替换的最大次数,默认 0 表示替换所有的匹配。
实例
#!/usr/bin/env python3
# -*- coding:utf-8 -*-
"""
@file : regex.py
@author : Leon(fxkxb.com)
@date : October 08, 2021 15:49
@version : 1.0
@description :
"""
import re
phone = "2004-959-559 # 这是一个国外电话号码"
# 删除字符串中的 Python注释
num = re.sub(r'#.*$', "", phone)
print("电话号码是: ", num)
# 删除非数字(-)的字符串
num = re.sub(r'\D', "", phone)
print("电话号码是 : ", num)
以上实例执行结果如下:
电话号码是: 2004-959-559
电话号码是 : 2004959559
3.4.1 repl 参数是一个函数
以下实例中将字符串中的匹配的数字乘以 2:
实例
#!/usr/bin/env python3
# -*- coding:utf-8 -*-
"""
@file : regex.py
@author : Leon(fxkxb.com)
@date : October 08, 2021 15:49
@version : 1.0
@description :
"""
import re
# 将匹配的数字乘以 2
def double(matched):
value = int(matched.group('value'))
return str(value * 2)
s = 'A23G4HFD567'
print(re.sub('(?P<value>\d+)', double, s))
执行输出结果为:
A46G8HFD1134
3.4.2 re.compile 函数
compile 函数用于编译正则表达式,生成一个正则表达式( Pattern )对象,供 match() 和 search() 这两个函数使用。
语法格式为:
re.compile(pattern[, flags])
参数:
- pattern : 一个字符串形式的正则表达式
flags : 可选,表示匹配模式,比如忽略大小写,多行模式等,具体参数为:
- re.I 忽略大小写
- re.L 表示特殊字符集 w, W, b, B, s, S 依赖于当前环境
- re.M 多行模式
- re.S 即为 . 并且包括换行符在内的任意字符(. 不包括换行符)
- re.U 表示特殊字符集 w, W, b, B, d, D, s, S 依赖于 Unicode 字符属性数据库
- re.X 为了增加可读性,忽略空格和 # 后面的注释
实例
>>>import re
>>> pattern = re.compile(r'\d+') # 用于匹配至少一个数字
>>> m = pattern.match('one12twothree34four') # 查找头部,没有匹配
>>> print m
None
>>> m = pattern.match('one12twothree34four', 2, 10) # 从'e'的位置开始匹配,没有匹配
>>> print m
None
>>> m = pattern.match('one12twothree34four', 3, 10) # 从'1'的位置开始匹配,正好匹配
>>> print m # 返回一个 Match 对象
<_sre.SRE_Match object at 0x10a42aac0>
>>> m.group(0) # 可省略 0
'12'
>>> m.start(0) # 可省略 0
3
>>> m.end(0) # 可省略 0
5
>>> m.span(0) # 可省略 0
(3, 5)
在上面,当匹配成功时返回一个 Match 对象,其中:
group([group1, …])
方法用于获得一个或多个分组匹配的字符串,当要获得整个匹配的子串时,可直接使用group()
或group(0)
;start([group])
方法用于获取分组匹配的子串在整个字符串中的起始位置(子串第一个字符的索引),参数默认值为 0;end([group])
方法用于获取分组匹配的子串在整个字符串中的结束位置(子串最后一个字符的索引+1),参数默认值为 0;span([group])
方法返回(start(group), end(group))
。
再看看一个例子:
实例
>>>import re
>>> pattern = re.compile(r'([a-z]+) ([a-z]+)', re.I) # re.I 表示忽略大小写
>>> m = pattern.match('Hello World Wide Web')
>>> print m # 匹配成功,返回一个 Match 对象
<_sre.SRE_Match object at 0x10bea83e8>
>>> m.group(0) # 返回匹配成功的整个子串
'Hello World'
>>> m.span(0) # 返回匹配成功的整个子串的索引
(0, 11)
>>> m.group(1) # 返回第一个分组匹配成功的子串
'Hello'
>>> m.span(1) # 返回第一个分组匹配成功的子串的索引
(0, 5)
>>> m.group(2) # 返回第二个分组匹配成功的子串
'World'
>>> m.span(2) # 返回第二个分组匹配成功的子串
(6, 11)
>>> m.groups() # 等价于 (m.group(1), m.group(2), ...)
('Hello', 'World')
>>> m.group(3) # 不存在第三个分组
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
IndexError: no such group
3.4.3 findall
在字符串中找到正则表达式所匹配的所有子串,并返回一个列表,如果没有找到匹配的,则返回空列表。
注意: match 和 search 是匹配一次 findall 匹配所有。
语法格式为:
findall(string[, pos[, endpos]])
参数:
- string : 待匹配的字符串。
- pos : 可选参数,指定字符串的起始位置,默认为 0。
- endpos : 可选参数,指定字符串的结束位置,默认为字符串的长度。
查找字符串中的所有数字:
实例
#!/usr/bin/env python3
# -*- coding:utf-8 -*-
"""
@file : regex.py
@author : Leon(fxkxb.com)
@date : October 08, 2021 15:49
@version : 1.0
@description :
"""
import re
pattern = re.compile(r'\d+') # 查找数字
result1 = pattern.findall('runoob 123 google 456')
result2 = pattern.findall('run88oob123google456', 0, 10)
print(result1)
print(result2)
输出结果:
['123', '456']
['88', '12']
3.4.4 re.finditer
和 findall 类似,在字符串中找到正则表达式所匹配的所有子串,并把它们作为一个迭代器返回。
re.finditer(pattern, string, flags=0)
参数:
参数 | 描述 |
---|---|
pattern | 匹配的正则表达式 |
string | 要匹配的字符串。 |
flags | 标志位,用于控制正则表达式的匹配方式,如:是否区分大小写,多行匹配等等。参见:正则表达式修饰符 - 可选标志 |
实例
#!/usr/bin/env python3
# -*- coding:utf-8 -*-
"""
@file : regex.py
@author : Leon(fxkxb.com)
@date : October 08, 2021 15:49
@version : 1.0
@description :
"""
import re
it = re.finditer(r"\d+", "12a32bc43jf3")
for match in it:
print(match.group())
输出结果:
12
32
43
3
3.4.5 re.split
split 方法按照能够匹配的子串将字符串分割后返回列表,它的使用形式如下:
re.split(pattern, string[, maxsplit=0, flags=0])
参数:
参数 | 描述 |
---|---|
pattern | 匹配的正则表达式 |
string | 要匹配的字符串。 |
maxsplit | 分隔次数,maxsplit=1 分隔一次,默认为 0,不限制次数。 |
flags | 标志位,用于控制正则表达式的匹配方式,如:是否区分大小写,多行匹配等等。参见:正则表达式修饰符 - 可选标志 |
实例
>>>import re
>>> re.split('\W+', 'runoob, runoob, runoob.')
['runoob', 'runoob', 'runoob', '']
>>> re.split('(\W+)', ' runoob, runoob, runoob.')
['', ' ', 'runoob', ', ', 'runoob', ', ', 'runoob', '.', '']
>>> re.split('\W+', ' runoob, runoob, runoob.', 1)
['', 'runoob, runoob, runoob.']
>>> re.split('a*', 'hello world') # 对于一个找不到匹配的字符串而言,split 不会对其作出分割
['hello world']
3.5 正则表达式对象
3.5.1 re.RegexObject
re.compile() 返回 RegexObject 对象。
3.5.2 re.MatchObject
group() 返回被 RE 匹配的字符串。
- start() 返回匹配开始的位置
- end() 返回匹配结束的位置
- span() 返回一个元组包含匹配 (开始,结束) 的位置
3.6 正则表达式修饰符 - 可选标志
正则表达式可以包含一些可选标志修饰符来控制匹配的模式。修饰符被指定为一个可选的标志。多个标志可以通过按位 OR(|) 它们来指定。如 re.I | re.M 被设置成 I 和 M 标志:
修饰符 | 描述 |
---|---|
re.I | 使匹配对大小写不敏感 |
re.L | 做本地化识别(locale-aware)匹配 |
re.M | 多行匹配,影响 ^ 和 $ |
re.S | 使 . 匹配包括换行在内的所有字符 |
re.U | 根据Unicode字符集解析字符。这个标志影响 w, W, b, B. |
re.X | 该标志通过给予你更灵活的格式以便你将正则表达式写得更易于理解。 |
4. 通用实例
4.1 校验中文
校验字符串中只能有中文字符(不包括中文标点符号)。中文字符的 Unicode 编码范围是 \u4e00
到 \u9fa5
。
如有兴趣,可以参考百度百科-Unicode 。
^[\u4e00-\u9fa5]+$
- 匹配: 春眠不觉晓
- 不匹配:春眠不觉晓,
4.2 校验身份证号码
身份证为 15 位或 18 位。15 位是第一代身份证。从 1999 年 10 月 1 日起,全国实行公民身份证号码制度,居民身份证编号由原 15 位升至 18 位。
- 15 位身份证:由 15 位数字组成。排列顺序从左至右依次为:六位数字地区码;六位数字出生日期;三位顺序号,其中 15 位男为单数,女为双数。
- 18 位身份证:由十七位数字本体码和一位数字校验码组成。排列顺序从左至右依次为:六位数字地区码;八位数字出生日期;三位数字顺序码和一位数字校验码(也可能是 X)。
身份证号含义详情请见:百度百科-居民身份证号码
4.3 地区码(6 位)
(1[1-5]|2[1-3]|3[1-7]|4[1-3]|5[0-4]|6[1-5])\d{4}
出生日期(8 位)
匹配18位身份证:
^((1[1-5]|2[1-3]|3[1-7]|4[1-3]|5[0-4]|6[1-5])\d{4})\d{4}((0[1-9]|1[0-2])(0[1-9]|[12]\d|3[01]))(\d{3}(\d|X))$
注:下面的是 18 位身份证的有效出生日期,如果是 15 位身份证,只要将第一个d{4}改为d{2}即可。
((\d{4}((0[13578]|1[02])(0[1-9]|[12]\d|3[01])|(0[13456789]|1[012])(0[1-9]|[12]\d|30)|02(0[1-9]|1\d|2[0-8])))|([02468][048]|[13579][26])0229)
15 位有效身份证
^((1[1-5]|2[1-3]|3[1-7]|4[1-3]|5[0-4]|6[1-5])\d{4})((\d{2}((0[13578]|1[02])(0[1-9]|[12]\d|3[01])|(0[13456789]|1[012])(0[1-9]|[12]\d|30)|02(0[1-9]|1\d|2[0-8])))|([02468][048]|[13579][26])0229)(\d{3})$
- 匹配:110001700101031
- 不匹配:110001701501031
18 位有效身份证
^((1[1-5]|2[1-3]|3[1-7]|4[1-3]|5[0-4]|6[1-5])\d{4})((\d{4}((0[13578]|1[02])(0[1-9]|[12]\d|3[01])|(0[13456789]|1[012])(0[1-9]|[12]\d|30)|02(0[1-9]|1\d|2[0-8])))|([02468][048]|[13579][26])0229)(\d{3}(\d|X))$
- 匹配:110001199001010310 | 11000019900101015X
- 不匹配:990000199001010310 | 110001199013010310
4.4 校验有效用户名、密码
描述:长度为 6-18 个字符,允许输入字母、数字、下划线,首字符必须为字母。
^[a-zA-Z]\w{5,17}$
- 匹配:he_llo@worl.d.com | hel.l-o@wor-ld.museum | h1ello@123.com
- 不匹配:hello@worl_d.com | he&llo@world.co1 | .hello@wor#.co.uk
4.5 校验邮箱
描述:不允许使用 IP 作为域名,如 : hello@154.145.68.12
@
符号前的邮箱用户和.
符号前的域名(domain)必须满足以下条件:
- 字符只能是英文字母、数字、下划线
_
、.
、-
; - 首字符必须为字母或数字;
_
、.
、-
不能连续出现。
域名的根域只能为字母,且至少为两个字符。
^[A-Za-z0-9](([_\.\-]?[a-zA-Z0-9]+)*)@([A-Za-z0-9]+)(([\.\-]?[a-zA-Z0-9]+)*)\.([A-Za-z]{2,})$
- 匹配:he_llo@worl.d.com | hel.l-o@wor-ld.museum | h1ello@123.com
- 不匹配:hello@worl_d.com | he&llo@world.co1 | .hello@wor#.co.uk
4.6 校验 URL
描述:校验 URL。支持 http、https、ftp、ftps。
^(ht|f)(tp|tps)\://[a-zA-Z0-9\-\.]+\.([a-zA-Z]{2,3})?(/\S*)?$
- 匹配:http://google.com/help/me | http://www.google.com/help/me/ | https://www.google.com/help.asp | ftp://www.google.com | ftps://google.org
- 不匹配:http://un/www.google.com/index.asp
4.7 校验时间
描述:校验时间。时、分、秒必须是有效数字,如果数值不是两位数,十位需要补零。
^([0-1][0-9]|[2][0-3]):([0-5][0-9])$
- 匹配:00:00:00 | 23:59:59 | 17:06:30
- 不匹配:17:6:30 | 24:16:30
4.8 校验日期
描述:校验日期。日期满足以下条件:
- 格式 yyyy-MM-dd 或 yyyy-M-d
- 连字符可以没有或是“-”、“/”、“.”之一
- 闰年的二月可以有 29 日;而平年不可以。
- 一、三、五、七、八、十、十二月为 31 日。四、六、九、十一月为 30 日。
^(?:(?!0000)[0-9]{4}([-/.]?)(?:(?:0?[1-9]|1[0-2])\1(?:0?[1-9]|1[0-9]|2[0-8])|(?:0?[13-9]|1[0-2])\1(?:29|30)|(?:0?[13578]|1[02])\1(?:31))|(?:[0-9]{2}(?:0[48]|[2468][048]|[13579][26])|(?:0[48]|[2468][048]|[13579][26])00)([-/.]?)0?2\2(?:29))$
- 匹配:2016/1/1 | 2016/01/01 | 20160101 | 2016-01-01 | 2016.01.01 | 2000-02-29
- 不匹配:2001-02-29 | 2016/12/32 | 2016/6/31 | 2016/13/1 | 2016/0/1
4.9 校验中国手机号码
描述:中国手机号码正确格式:11 位数字。
移动有 16 个号段:134、135、136、137、138、139、147、150、151、152、157、158、159、182、187、188。其中 147、157、188 是 3G 号段,其他都是 2G 号段。联通有 7 种号段:130、131、132、155、156、185、186。其中 186 是 3G(WCDMA)号段,其余为 2G 号段。电信有 4 个号段:133、153、180、189。其中 189 是 3G 号段(CDMA2000),133 号段主要用作无线网卡号。总结:13 开头手机号 0-9;15 开头手机号 0-3、5-9;18 开头手机号 0、2、5-9。
此外,中国在国际上的区号为 86,所以手机号开头有+86、86 也是合法的。
以上信息来源于 百度百科-手机号
^((\+)?86\s*)?((13[0-9])|(15([0-3]|[5-9]))|(18[0,2,5-9]))\d{8}$
- 匹配:+86 18012345678 | 86 18012345678 | 15812345678
- 不匹配:15412345678 | 12912345678 | 180123456789
4.10 校验中国固话号码
描述:固话号码,必须加区号(以 0 开头)。 3 位有效区号:010、020~029,固话位数为 8 位。 4 位有效区号:03xx 开头到 09xx,固话位数为 7。
如果想了解更详细的信息,请参考 百度百科-电话区号 。
^(010|02[0-9])(\s|-)\d{8}|(0[3-9]\d{2})(\s|-)\d{7}$
- 匹配:010-12345678 | 010 12345678 | 0512-1234567 | 0512 1234567
- 不匹配:1234567 | 12345678
4.11 校验 IPv4 地址
描述:IP 地址是一个 32 位的二进制数,通常被分割为 4 个“8 位二进制数”(也就是 4 个字节)。IP 地址通常用“点分十进制”表示成(a.b.c.d)的形式,其中,a,b,c,d 都是 0~255 之间的十进制整数。
^([01]?\d\d?|2[0-4]\d|25[0-5])\.([01]?\d\d?|2[0-4]\d|25[0-5])\.([01]?\d\d?|2[0-4]\d|25[0-5])\.([01]?\d\d?|2[0-4]\d|25[0-5])$
- 匹配:0.0.0.0 | 255.255.255.255 | 127.0.0.1
- 不匹配:10.10.10 | 10.10.10.256
4.12 校验 IPv6 地址
描述:IPv6 的 128 位地址通常写成 8 组,每组为四个十六进制数的形式。
IPv6 地址可以表示为以下形式:
- IPv6 地址
- 零压缩 IPv6 地址(section 2.2 of rfc5952)
- 带有本地链接区域索引的 IPv6 地址 (section 11 of rfc4007)
- 嵌入 IPv4 的 IPv6 地址(section 2 of rfc6052
- 映射 IPv4 的 IPv6 地址 (section 2.1 of rfc2765)
- 翻译 IPv4 的 IPv6 地址 (section 2.1 of rfc2765)
显然,IPv6 地址的表示方式很复杂。你也可以参考:
(([0-9a-fA-F]{1,4}:){7,7}[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,7}:|([0-9a-fA-F]{1,4}:){1,6}:[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,5}(:[0-9a-fA-F]{1,4}){1,2}|([0-9a-fA-F]{1,4}:){1,4}(:[0-9a-fA-F]{1,4}){1,3}|([0-9a-fA-F]{1,4}:){1,3}(:[0-9a-fA-F]{1,4}){1,4}|([0-9a-fA-F]{1,4}:){1,2}(:[0-9a-fA-F]{1,4}){1,5}|[0-9a-fA-F]{1,4}:((:[0-9a-fA-F]{1,4}){1,6})|:((:[0-9a-fA-F]{1,4}){1,7}|:)|fe80:(:[0-9a-fA-F]{0,4}){0,4}%[0-9a-zA-Z]{1,}|::(ffff(:0{1,4}){0,1}:){0,1}((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\.){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])|([0-9a-fA-F]{1,4}:){1,4}:((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\.){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9]))
- 匹配:1:2:3:4:5:6:7:8 | 1:: | 1::8 | 1::6:7:8 | 1::5:6:7:8 | 1::4:5:6:7:8 | 1::3:4:5:6:7:8 | ::2:3:4:5:6:7:8 | 1:2:3:4:5:6:7:: | 1:2:3:4:5:6::8 | 1:2:3:4:5::8 | 1:2:3:4::8 | 1:2:3::8 | 1:2::8 | 1::8 | ::8 | fe80::7:8%1 | ::255.255.255.255 | 2001:db8:3:4::192.0.2.33 | 64:ff9b::192.0.2.33
- 不匹配:1.2.3.4.5.6.7.8 | 1::2::3
5. 工具
- Regexper : https://regexper.com/
- Regulex : https://jex.im/regulex/
- RegExr : https://regexr.com/
6. 参考
- 正则表达式 - 维基百科 : https://zh.wikipedia.org/wiki/%E6%AD%A3%E5%88%99%E8%A1%A8%E8%BE%BE%E5%BC%8F
- Regular expression - Wikipedia : https://en.wikipedia.org/wiki/Regular_expression
- 正则表达式 - 百度百科 : https://baike.baidu.com/item/%E6%AD%A3%E5%88%99%E8%A1%A8%E8%BE%BE%E5%BC%8F
- 正则表达式 - 百度百科 : https://baike.baidu.com/item/%E6%AD%A3%E5%88%99%E8%A1%A8%E8%BE%BE%E5%BC%8F
- 正则表达式 – 教程 | 菜鸟教程 : https://www.runoob.com/regexp/regexp-tutorial.html
- Java 正则表达式 | 菜鸟教程 : https://www.runoob.com/java/java-regular-expressions.html
- Java 正则从入门到精通 : https://github.com/dunwu/javacore/blob/master/docs/advanced/java-regex.md
- Python 正则表达式 | 菜鸟教程 : https://www.runoob.com/python/python-reg-expressions.html
太长了不看