正则表达式是用于搜索文本的技巧,对于任何需要使用计算机办公的人来讲,掌握一定量的正则表达式都有助于提升工作效率
正则表达式(Regular Expression)常缩写为RE
,是计算机科学的一个文本搜索概念
正则表达式使用字符串来描述一系列匹配某个句法规则的字符串,在很多文本编辑器里,正则表达式通常被用来检索、替换匹配某个模式的文本
这篇笔记以最简单的方式介绍回顾正则表达式的例子,适合速通或偶尔查阅
最后会顺带介绍python
中的正则表达式使用方法
1. 语法
阅读下面这篇文章,下面的所有的问题与示例都会围绕着这篇文章进行
In the era of advanced communication technology, phone numbers play a vital role in connecting people worldwide. Take, for example, the number "010-20193201." This local phone number, commonly used in certain regions, helps individuals stay connected within their community. On the other hand, the international number "+13123029301" demonstrates the global reach of modern telecommunication. With this number, people can bridge continents and communicate effortlessly across borders. Whether it's a local number like "010-20193201" or an international one like "+13123029301," phone numbers facilitate seamless communication, breaking down barriers and bringing people closer together in this interconnected world.
1.1. 元字符
元字符
是正则表达式最基础的构成,举一个最简单的例子,匹配"hello world"中的world,可以使用 \bworld\b
匹配进行匹配
\b
就是元字符之一,表示匹配单词的开始,元字符的列表如下
代码 | 说明 |
---|---|
. | 匹配除了换行符以外的任意字符 |
\w | 匹配字母/数字/下划线/汉字 |
\s | 匹配任意空白符 |
\d | 匹配任意数字 |
\b | 匹配单词的开始/结束 |
^ | 匹配字符串的开始 |
$ | 匹配字符串的结束 |
[abc] | 匹配abc中任意一个字符,abc也可换成其他特定字符 |
\ | 转义符,如想匹配问号,则是’\?' |
元字符列表除了表达匹配的元字符
外,还包括以下表达重复的元字符
代码 | 说明 |
---|---|
* | 重复0次或者多次 |
+ | 重复1次或者多次 |
? | 重复0次或者1次 |
{n} | 重复N次 |
{n,} | 重复N次或者更多次 |
{n,m} | 重复N次或者M次 |
问题
- 请匹配出文章中的010开头的8位数号码
- 匹配文中所有大写开头的单词
- 请匹配出单词中包含he(不包括单词he)的单词
答案
\b010-\d{8}\b
\b[A-Z]\w*\b
\b\w*he\w*\b
1.2. 反义
对于元字符\d
的意思是匹配所有任意数字,如果需要匹配所有非数字呢?此时即是反义
代码 | 说明 |
---|---|
\W | 匹配所有不是字母/数字/下划线/汉字的字符,等价于[^A-Za-z0-9_] |
\S | 匹配所有不是空白符的条件,等价于[^ \f\n\r\t\v] |
\D | 匹配所有非数字的字符,等价于[^0-9] |
\B | 匹配不是单词开头或结束的位置 |
[^abc] | 匹配除了abc以外的所有字符 |
问题
- 匹配文中所有不含t字母的单词
答案
\b[^t\s]+\b
1.3. 范围匹配
除去上面的基础字符,还有一些常用的范围匹配写法,如下
代码 | 说明 |
---|---|
[a-z] | 匹配“a”到“z”范围内的任意小写字母字符 |
[A-Z] | 匹配“A”到“Z”范围内的任意大写字母字符 |
[0-9] | 匹配“0”到“9”范围内的所有数字 |
1.4. 分组
上面的语法已经足够解决绝大部分正则判断了,但需要多种情况的判定则需要更复杂一些的语法字符来辅助判断
代码 | 说明 |
---|---|
| | 分支条件,当筛选的数据有多种规则时,满足其中一种规则即可当成匹配 |
() | 子表达式,也称“分组” |
分组是可以被命名的,分为2种情况,手动跟自动
自动命名分组,可以使用圆括号将要匹配的模式括起来,从而创建一个分组,例如,使用正则表达式来匹配电话号码中的区号和本地号码(\d{3})-(\d{8})
,其中第一个分组 (\d{3})
是匹配区号,第二个分组 (\d{8})
是匹配本地号码,这种情况下,分组会按照在正则表达式中出现的顺序从1开始自动进行编号
手动命名分组使用 ?P<name>
的语法来手动命名一个分组,例如,可以使用正则表达式来匹配电话号码中的国际代码和号码\+(?P<country>\d{2})(?P<number>\d+)
,其中,(?P<country>\d{2})
命名了一个名为 country
的分组用于匹配国际代码,(?P<number>\d+)
命名了一个名为 number
的分组用于匹配号码
1.5. 零宽度断言
零宽度断言(zero-width assertions)是正则表达式中一种特殊的语法,用于限定匹配的位置而不消费任何字符,它们被称为零宽度断言,因为它们只检查当前位置前面或后面的字符,而不会在匹配结果中包含这些字符 零宽度断言可以用来指定一个位置,该位置需要满足某些条件才能匹配成功,它们不会实际匹配字符,而是在特定的位置进行条件判断,根据判断结果,正则表达式可以决定是否继续匹配常见的零宽度断言包括:
- 正向肯定断言(Positive Lookahead):
(?=...)
,表示在当前位置后面的字符能够匹配...
,它用于查找符合某种模式的后面位置- 正向否定断言(Negative Lookahead):(?!...)
,表示在当前位置后面的字符不能匹配...
,它用于查找不符合某种模式的后面位置 - 反向肯定断言(Positive Lookbehind):
(?<=...)
,表示在当前位置前面的字符能够匹配...
,它用于查找符合某种模式的前面位置 - 反向否定断言(Negative Lookbehind):
(?<!...)
,表示在当前位置前面的字符不能匹配...
,它用于查找不符合某种模式的前面位置
举例从文章中抽取所有区号,可以使用\d{3}(?=-\d+)
问题
- 匹配所有有区号号码但不包括区号
- 匹配所有的手机号码但不包括区号
答案
(?<=\d{3}-)\d+
(?<=\+1)\d{10}
使用零宽度断言可以更精确地指定匹配位置,提供更强大的正则表达式模式匹配能力,但需要注意,不是所有的正则表达式引擎都支持零宽度断言,因此在使用时需要注意兼容性
1.6. 懒惰和贪婪
在正则表达式中,贪婪和懒惰是指匹配模式时的不同行为,默认情况下,正则表达式是贪婪模式
贪婪模式
是指正则表达式尽可能地匹配最长的字符串,例如在文章中,使用正则表达式 /(\d+-\d+)/
来匹配电话号码,它将匹配到最长的字符串 “010-20193201”,而不是只匹配到 “010” 或 “20193201”
懒惰模式
是指正则表达式尽可能地匹配最短的字符串,使用正则表达式 /(\d+-\d+?)/
来匹配电话号码,它将匹配到最短的字符串 “010” 和 “20193201”,而不是整个电话号码
在正则表达式中,我们可以使用 ?
符号来表示懒惰模式,使得匹配尽可能短,如果不使用 ?
符号,默认为贪婪模式
2. Python正则表达式 - RE模块
Python中的RE模块提供与Perl语言类似的正则表达式匹配
Python字符串中使用反斜杠\
来转义特殊符号,以免引发其特殊含义,例如搜索单引号的Python字符串要写成search_re = 'Chancel\'s blog'
但是正则表达式也采用了反斜杠\
来表达转义,那么如果需要在Python中的正则表达式字符串放入单引号,则面临需要写成search = 'Chancel\\'s blog'
的尴尬情况
为了使字符串中的反斜杠不被转义,Python允许使用在字符串前面添加r
来表示字符串中的反斜杠不被转义,变成search_re = r'chancel\'s blog'
正则表达式的详细文档可以参考 官方文档 | re – 正则表达式
以下说明基于Python版本3.9.2挑选几个重要的方法作为例子
2.1. compile
方法 re.compile(pattern, flags=0)可以编译正则表达式,用于匹配,在多次使用正则表达式的场景下可以提前编译正则表达式来提高匹配效率
例如匹配问号前的单词
import re
content = 'Can you turn the declarative sentence into a rhetorical question?'
re_compile = re.compile(r'\b\w+\?', re.I)
print(re_compile)
# output: re.compile('\\b\\w+\\?', re.IGNORECASE)
Flag参数
标识 | 含义 |
---|---|
re.A | ASCII,让 \w, \W, \b, \B, \d, \D, \s 和 \S 只匹配ASCII |
re.I | IGNORECASE,忽略大小写 |
re.L | LOCALE,由当前语言区域决定 \w, \W, \b, \B 和大小写敏感匹配 |
re.M | MULTILINE,让’^‘和’$‘匹配每一行 |
re.S | DOTALL,让’.‘匹配所有字符,包含换行符! |
re.X | VERBOSE,允许添加注释、空白符 |
2.2. search
方法re.search是非常常用的一个方法,用于匹配第一个相应的匹配对象
import re
result = re.search(r'o','oooo')
print(result.group(0))
# output: o
2.3. split
split可以将字符串按正则表达式全部分组
import re
>>> re.split(r'\W+', 'Words, words, words.')
['Words', 'words', 'words', '']
>>> re.split(r'(\W+)', 'Words, words, words.')
['Words', ', ', 'words', ', ', 'words', '.', '']
>>> re.split(r'\W+', 'Words, words, words.', 1)
['Words', 'words, words.']
>>> re.split('[a-f]+', '0a3B9', flags=re.IGNORECASE)
['0', '3', '9']
Python的正则表达式还有非常多的知识,如果正则表达式基础过关,也可以直接参考官方例子
3. 尾声
参考资料