正则表达式
计算机科学领域有个笑话:“如果你有一个问题打算用正则表达式来解决,那么就是两个问题了。”
然而,正则表达式(通常简写 regex)经常被嘲笑是一堆随机符号混杂在一起,看起来毫无意义。这种印象让人们对其避而远之,然后费尽心思写一堆复杂的查找和过滤函数,其实他们真正需要的就是一行正则表达式。
其实正则表达式上手一点也不难,而且运行很快,通过一些简单的例子就可以轻松地学会。
尝试正则表达式
之所以叫正则表达式,是因为它们可以识别正则字符串(regular string);也就是说,它们可以这么定义:“如果你给我的字符串符合规则,我就返回它”,或者是“如果字符串不符合规则,我就忽略它”。这在快速浏览大文档,以查找像电话号码和邮箱地址之类的字符串时,是非常方便的。
注意这里我用了一个词组正则字符串。什么是正则字符串?其实就是任意可以用一系列线性规则构成的字符串,就像:
- 字母“a”至少出现一次;
- 后面跟着字母“b”,重复五次;
- 后面再跟字母“c”,重复任意偶数次;
- 最后一位是字母“d”或“e”。
满足上面规则的字符串有“aaaabbbbbccccd”“aabbbbbcce”等。
正则表达式就是表达这种规则的一种快捷方式。这组规则的正则表达式如下所示:
aa*bbbbb(cc)*(d|e)
乍一看这个字符串会觉得有点奇葩,但是当我们把它分解之后就会很清楚了。
aa*
- a 后面跟着 a*(读作 a 星)表示“重复任意次 a,包括 0 次”。这样就可以保证字母 a 至少出现一次。
bbbbb
- 这没什么特别的,就是 5 个 b。
(cc)*
- 任意偶数个字符都可以编组,这个规则是用括号括住两个 c,然后后面跟一个星号,表示可以有任意次两个 c(也可以是 0 次)。
(d|e)
- 在两个表达式中间加一个竖线(|)表示“这个或那个”。本例中表示“增加一个 d 或者一个 e”。这样就可以保证字符串的结尾是这两个字母之一。
常用的正则表达式的符号
下表列出了常用的正则表达式符号以及简短的解释和示例。这个列表中并没有囊括全部的正则表达式,不同语言中的正则表达式符号会略有不同。但是,这里列出的 12 个符号是 Python 中最常用的正则表达式符号,可以用于查找和获取几乎任意字符串类型。
符号 | 含义 | 例子 | 匹配结果 |
---|---|---|---|
* | 匹配前面的字符、子表达式或括号里的字符 0 次或多次 | a* b* | aaaaaaaaaaa,aaabbbbbb,bbbbbbb |
+ | 匹配前面的字符、子表达式或括号里的字符至少 1 次 | a+b+ | aaaaaaaaab,aaabbbbb,abbbbb |
[ ] | 匹配中括号里的任意一个字符(相当于“任选一个”) | [A-Z]* | APPLE,CAPITALS,QWERTY |
() | 表达式编组(在正则表达式的规则里编组会优先运行) | (a* b)* | aaabaab,abaaab,ababaaab |
{m,n} | 匹配前面的字符、子表达式或括号里的字符m到n次(包括m或n) | a{2,3}b{2,3} | aabbb,aaabbb,aabb |
[ ^ ] | 匹配任意一个不在中括号里的字符 | [^A-Z]* | apple,lowercase,qwerty |
| | 匹配任意一个由竖线分隔的字符、子表达式(注意是竖线,不是大写字母I) | b(a|i|e)d | bad,bid,bed |
. | 匹配任意单个字符(包括符号、数字和空格等) | b.d | bad,bzd,b$d,b d |
^ | 指字符串开始位置的字符或子表达式 | ^a | apple,asdf,a |
\ | 转义字符(把有特殊含义的字符转换成字面形式) | \.\ |\\ | .|\ |
$ | 经常用在正则表达式的末尾,表示“从字符串的末尾端匹配”。如果不用它,每个正则表达式实际都带着“.*”模式,只会从字符串开头进行匹配。这个符号可以看成是 ^ 符号的反义词。 | [A-Z]*[a-z] * $ | ABCabc,zzzyx,Bob |
?! | “不包括”。这个奇怪的组合通常放在字符或正则表达式的前面,表示字符不能出现在目标字符串。这个符号比较难用,毕竟字符通常会在字符串的不同部位出现。如果要在整个字符串中彻底排除某个字符,就加上 ^ 和 $ 符号 | ^((?![A-Z]).)*$ | no-caps-here,a4ef!ne |
正则表达式在实际中的一个经典应用是识别邮箱地址。虽然不同邮箱服务器的邮箱地址的具体规则不尽相同,但是我们还是可以创建几条通用规则。每条规则对应的正则表达式如下表所示:
规则 | 正则表达式 |
---|---|
1.邮箱地址的第一部分至少包括一种内容:大写字母、小写字母、数字 0-9 、点号(.)加号(+)或下划线(_) | [A-Za-z0-9\ . _+]+:这个正则表达式简写非常智慧。例如,它用 “A-Z” 表示“A-Z中的任意大写字母”。把所有可能的序列和符号放在中括号(而不是小括号)里表示“可以是方括号中任何一个符号”。要注意后面的加号,它表示“这些符号都可以出现多次,但至少要出现一次” |
2.之后,邮箱地址会包含一个@符号 | @:这个符号很简单:@符号必须出现在中间位置,并且只能出现一次 |
3.在符号@之后,邮箱地址还必须至少包含一个大写或小写字母 | [A-Za-z]+:可能只在域名的前半部分、符号@后面用字母。而且,至少要有一个字符 |
4.之后跟一个点号(.) | \.:在域名之前必须有一个点号(.)。退格在这里用作转义字符 |
5.最后邮箱地址用 com、org、edu、net 结尾(实际上,顶级域名有很多种可能,这里作为示例只演示这四个就够用了) | (com|org|edu|net):这样列出了后半部分邮箱地址中可能出现在点号之后的字母序列 |
把上面的规则连接起来,就获得了完整的正则表达式:
[A-Za-z0-9\._+]+@[A-Za-z]+\.(com|org|edu|net)
当动手开始写正则表达式的时候,最好先写一个步骤列表,具体描述出你的目标字符串结构。还要注意处理一些细节。比如,当你识别电话号码的时候,会考虑国家代码和分机号吗?