如果对你有帮助,就点个赞吧~
正则表达式是日常文本查询、编辑以及代码开发过程中常用的一个编程语言。
本文档旨在介绍正则表达式的日常使用场景以及表达式编写。
以下将会先通过几个使用案例来说明正则表达式的使用, 之后再进行"元字符", “匹配模式”, "环视断言"等具体内容的介绍
如果是学完之后回头过来复习,希望快速回顾并练习正则知识点的同学,可以浏览正则练习网站
正则表达式使用样例1: 获取手机号码(或其他含义的数字串)的末尾两位数字
- 正则表达式: “\d{2}$”
- 输入输出测试用例:
"""Regular Expression Use Case in python"""
import re
from typing import List
RE_GET_THE_TAIL_TWO_DIGITS_IN_PHONE_NUMBER = re.compile(r"\d{2}$")
def find_target_piece_content_by_regexp(content):
matched_results: List[str] = RE_GET_THE_TAIL_TWO_DIGITS_IN_PHONE_NUMBER.findall(content)
return matched_results
输入文本 | 匹配命中的文本 |
---|---|
1234 | 34 |
123 | 23 |
12 | 12 |
1 | 无匹配结果 |
正则表达式使用样例2: 检查是否包含"自然人"关键词, 且需要排除"非自然人"的情况
- 正则表达式: “(?<! 非)自然人”
- 输入输出测试用例:
"""Regular Expression Use Case in python"""
import re
from typing import List
RE_KEYWORD_PERSON = re.compile(r"(?<!非)自然人")
def find_target_piece_content_by_regexp(content):
matched_results: List[str] = RE_KEYWORD_PERSON.findall(content)
return matched_results
输入文本 | 匹配命中的文本 |
---|---|
自然人 | 自然人 |
大自然人 | 自然人 |
自然 | 无匹配结果 |
非自然人 | 无匹配结果 |
普通字符与元字符
正则表达式中使用的字符分为两类: 普通字符和元字符。
普通字符所代表的字符与其字面含义一致, 如"d"就是代表英文字母"d"
元字符则代表了一类字符或一个字符集, 如"\d"表达了英文半角数字集合"0", “1”, …, “9”
除了像"\d"这种内置的(或者说约定的)元字符可以表示一组字符的集合外, 使用者也可以自定义字符集来表示某个位置可能是某个字符集合中的任意元素, 通过在中括号"[]“中填写指定的字符来形成字符集合。比如, “\w"表示"所有大写英文字母, 所有小写英文字母以及下划线”, 如果我们希望有一个字符集合表示"所有大写英文字母, 所有小写英文字母”(不包括下划线), 则我们可以将该字符集合表示为"[A-Za-z]"("-“表示连续的字符或者说字符范围,即"0-9"表示数字0到数字9这个十个数字。如果要在字符集合中表示”-“本身, 需要把”-“放在括号开头或者末尾, 比如”[-9]“表示”-“和"9”)
元字符不需要特地花时间记忆, 通过多次查阅和使用正则表达式解决问题, 自然会记住较常使用的元字符
正则中常用的元字符见 附录A 正则元字符
替代选项
给定一个场景: 要匹配一段文本中是否有关键词"北京"或者"天津"
如果我们按照之前的内容, 可以使用两个正则表达式"北京"和"天津"来完成这个任务。
如果我们希望只使用一个正则表达式来达到同样的效果, 则需要使用表示"替代选项"的元字符"|"
- 正则表达式: “北京|天津”
- 说明: (搜索或匹配) 正则将搜索文本中的"北京", 如果不存在"北京", 则搜索替代目标"天津"。(如果是全提取,则会提取文本中的所有"北京"以及"天津")
- python中的匹配对应re.match, 表示从文本开头就匹配指定正则表达式的文本
- python中的搜索对应re.search, 表示文本中匹配第一个符合指定正则表达式的文本
- python中的全提取对应re.findall, 表示文本中提取所有符合指定正则表达式的文本
输入文本 | 匹配命中的文本 |
---|---|
北京 | 北京 |
天津 | 天津 |
北京和天津 | 北京 |
北天津 | 天津 |
北京津 | 北京 |
北津 | 无匹配结果 |
分组
给定一个场景: 要匹配一段文本中是否有关键词"北京市"或者"天津市"
如果我们按照之前的内容, 可以使用两个正则表达式"北京市"和"天津市"来完成这个任务。
或者使用替代选项的写法"北京市|天津市", 可以只使用一个正则表达式达到预期效果。
进一步, 如果我们认为"北京市|天津市"中有重复的部分(北京市的"市"和天津市的"市"), 并且希望写出一个正则表达式可以免掉这部分重复内容, 换句话说, 我们希望表示"北京市"中"北京"的替代选项是"天津"(之前的表达式表示的是: “北京市"的替换选项是"天津市”)
于是我们需要只对"北京"这部分内容使用替代选项。为了达到这个目的,我们需要明确表示"北京"是我们要使用替代选项的"部分", 这需要使用到"分组" (分组的其中一个功能是表示整体)
分组使用小括号表示, 被小括号括起来的内容表示是一个组/一个整体
正则表达式: “(北京|天津)市”
反引用
给定一个场景: 要匹配一段文本中是否有多次出现的关键词"红色"或者"蓝色"
简化一个这个场景, 给定一段话"我在{颜色1}中看见了{颜色2}“, 希望当颜色1和颜色2是相同的时候提取出该颜色。
如果我们写成"我在(红色|蓝色)中看见了(红色|蓝色)”, 则以下四句话都可以被这个正则命中:
- 我在红色中看见了红色
- 我在蓝色中看见了蓝色
- 我在红色中看见了蓝色
- 我在蓝色中看见了红色
但我们其实只希望在第1和第2句话中命中正则。换句话说, 我们在{颜色2}的位置, 想表示的不是"红色或蓝色", 而是前面{颜色1}命中的颜色。但是在命中之前,我们无法知道命中的是哪一个颜色, 所以无法用"普通字符"表示命中的颜色。
要表示"前面的分组命中的内容", 就需要使用"反引用".
“反引用"使用反斜杠”\"+分组序号表示, 比如"\1"和"\2"分别表示第一个和第二个分组的内容
对于嵌套分组(或者说嵌套括号)的情况,明确某个括号是第几个分组的简便方法是: 数这个分组的左括号是正则表达式从开头开始数起,第几个出现的左括号, 比如: "((北)|(京)|(市))"中, "北"和"京"所在分组分别是第2和第3个分组
于是,我们用来完成上述场景的正则表达式写为: “我在(红色|蓝色)中看见了\1”
量词
给定一个场景: 要匹配一段文本中的"吃饭", 但这段文本中可能存在娘娘腔语气的台词, 比如: “吃饭饭”, 这种情况需要优先提取"吃饭饭"
也就是说, 吃饭后面可能会有(也可能没有)一个"饭"
如果你是一个娘娘腔, 并觉得上面的例子冒犯了你,我感到很抱歉,并特地为你举另一个例子以表示歉意:
给定一个场景: 要匹配一段文本中的"北京"或者"北京市", 存在"北京市"的时候要优先提取"北京市"而不是"北京"
如果使用"替代选项"的写法, 我们可以将表达式写为: “(北京市|北京)” (注意,"北京市"要放在前面)
上面的写法可能会跟之前"北京市|天津市"的例子一样, 希望摆脱掉"北京"这部分重复内容的编写。基于这个诉求,我们重新像考虑"吃饭"和"吃饭饭"之间的关系一样考虑"北京市"和"北京"之间的关系,我们会意识到, “北京市"其实就是"北京"后面有一个"市”, “北京"其实就是"北京"后面没有一个"市”(如果你觉得这是一句废话, 那么我同意你的看法)。
正则中, 表示出现次数的记号被称作"量词"。这种"可能有也可能没有"在正则表达式中会被转述为"出现0次或出现多次", 表示为正则符号为: “{0, 1}”(表示出现0~1次,或者说最少出现0次,最多出现1次)
于是满足要求的正则表达式可以写为: “北京市{0,1}”(或者"吃饭饭{0,1}")
另外, 在量词表示某些特殊次数的情况下, 会有等价写法, 比如:
量词 | 含义 | 等价写法 |
---|---|---|
? | 出现0次或1次 | {0,1} |
* | 出现0次或任意次(大于0次) | {0,} |
+ | 出现任意次(大于0次) | {1,} |
边界与位置
给定一个场景: 要匹配一段文本中的"北京", 且要求匹配到的"北京"必须在行开头, 其他位置的"北京"不做提取
这种**“对目标内容所在位置有要求”**的情况, 需要使用到正则中匹配位置的记号。比如上述要求"文本在行开头"在正则中使用托字符"^“表示, 正则表达式写为: “^北京”
“行开头"只是"位置"的一种, 诸如此类的还有"行末尾”, “单词边界开始位置"等等。
需要额外再说明一下的是"单词边界”, 正则中的单词边界记号”\b"将会匹配"空格", “行首”, "行末"等这些符合一个单词左右两边的边界可能情况。
需要注意的是, 实际上"\b"匹配的是一个0宽非实体, 通俗来说, 返回结果中不会包含"\b"匹配的内容(比如空格)。
环视
给定一个场景: 要匹配一段文本中的"很笨", 但是不能匹配文本中"我很笨"的"很笨", 因为我不想承认"我很笨"
这种对某个表达式"前或后"的内容"是或不是"某个指定内容的要求, 就需要用环视断言来完成, 根据断言的位置(前或后), 断言的正负向(等于与不等于指定内容),排列组合形成四种断言类型:
断言类型 | 表示的含义 | 写法 | 如果使用提取, 会返回的文本 |
---|---|---|---|
正向前视断言 | 表达式右边内容等于指定内容 | 我吃了(?=鸡翅) | 我吃了 |
负向前视断言 | 表达式右边内容不等于指定内容 | 我吃了(?!鸡翅) | 我吃了 |
正向后视断言 | 表达式左边内容等于指定内容 | (?<=鸡翅)我吃了 | 我吃了 |
负向后视断言 | 表达式左边内容不等于指定内容 | (?<!鸡翅)我吃了 | 我吃了 |
于是, 满足要求的正则表达式可以写为: “(?<!我)很笨”
如果环视断言的记忆对你有一些难度, 并且你更喜欢记得住环视断言的自己, 那么以下记忆逻辑或许可以帮到你:
- 问号像是用户自定义正则某些配置的标记符
- 是是等号(=), 非是叹号(!)
- 想在哪边断言就把断言放哪边
- 放在左边的时候需要在等号或不等号的左边加上左尖括号跟正则引擎确认, 这个断言确实要放左边
附录A 正则元字符
正则匹配单个字符
元字符 | 名称 | 匹配功能 |
---|---|---|
. | 点 | 任意单个字符 |
[…] | 字符类 | 匹配中任意括号中列举的字符 |
[^…] | 负向字符类 | 匹配中任意括号中没有列举的字符 |
\char | 转义字符 | 匹配反斜杠之后的字符 |
正则匹配位置
元字符 | 名称 | 匹配功能 |
---|---|---|
^ | 托字符 | 行首 |
$ | 美元符 | 行末 |
\< | 反斜杠+小于号 | 单词开头 |
\> | 反斜杠+大于号 | 单词结尾 |
正则匹配数量(量词)
元字符 | 名称 | 匹配功能 |
---|---|---|
? | 问号 | 可选, 前一次表达式出现一次或不出现 |
* | 星号 | 前一个表达式出现任意次数,包括零次 |
+ | 加号 | 前一个表达式出现1次或多次 |
{N} | 精确次数匹配 | 匹配刚好N次 |
{N, } | 最小次数匹配 | 至少匹配N次 |
{min, max} | 指定次数区间匹配 | 匹配次数区间为[min, max] |
其他正则元字符
元字符 | 名称 | 匹配功能 |
---|---|---|
| | 替代选项 | 依次匹配给定表达式的某一个 |
- | 英文破折号 | 表示范围 |
(…) | 小括号 | 限制替代选项的范围 |
\1, \2, … | 反引用 | 匹配之前小括号匹配到的文本 |
\b | 单词边界 | 匹配标记单词结束的一批符号 |
\B | 反斜杠 | "\\"的替代写法 |
\w | 单词字符 | 英文字符, 数字, 下划线 |
\W | 非单词字符 | 非"英文字符, 数字, 下划线" |
\` | buffer开头 | 匹配传给grep的buffer头 |
\’ | buffer结尾 | 匹配传给grep的buffer尾 |