文章目录
- 简介
- 不可见的普通字符
- 元字符
- 语法
- 运算符优先级
- 常用示例
- 参考文献
简介
正则表达式(Regular Expression),又称规则表达式,在代码中常简写作 regex、regexp 或 RE。正则表达式通常用来检索、替换那些符合某个模式(规则)的文本。常用的程序设计语言都支持正则表达式,比如 C 11 也将正则表达式纳入标准,Perl、Python、PHP、Javascript、Ruby 等脚本语言都内置了强大的正则表达式处理引擎,Java、C#、Go、Delphi 等编译型语言都支持正则表达式。
正则表达式由一些普通字符和一些元字符(Meta Characters)组成。普通字符包括可打印字符(大小写的字母、数字、部分特殊字符)和一些不可打印的字符(比如换行符,制表符Tab和空格等),以及正则表达式中规定的特殊字符。而元字符则在正则表达式中具有特殊的含义,下面会给予解释。
不可见的普通字符
不可见字符也是正则表达式的组成部分。下表列出了常见的不可见字符的转义序列:
字符 | 含义 |
---|---|
cx | 匹配由x指明的控制字符。例如,cM匹配一个回车符(^M,Control M)。x 的值必须为 A-Z 或 a-z 之一。否则,将 c 视为一个原义的 ‘c’ 字符 |
t | 匹配一个制表符。等价于 x09 和 cI |
n | 匹配一个换行符。等价于 x0a 和 cJ |
v | 匹配一个垂直制表符。等价于 x0b 和 cK |
f | 匹配一个换页符。等价于 x0c 和 cL |
r | 匹配一个回车符。等价于 x0d 和 cM |
元字符
元字符是正则表达式的特殊字符,具有特殊含义,是正则表达式的重要组成部分。下表说明了常用元字符的含义与作用
:
元字符 | 描述 | 示例 |
---|---|---|
将一个字符标记为特殊字符、或一个原义字符、或一个后向引用、或一个八进制转义符 | \n匹配n。n匹配换行符。\匹配。77匹配字符 ? | |
^ | 匹配字符串的开始位置 | |
$ | 匹配字符串的结束位置 | |
* | 匹配前面的子表达式零次或多次(>=0次) | zo* 匹配 z 或 zo 或 zoo |
| 匹配前面的子表达式一次或多次(>=1次) | zo 匹配 zo 或 zoo,但不能匹配 z。 等价于 {1,} |
? | 匹配前面的子表达式零次或一次 | zo? 匹配 z 或 zo 。? 等价于 {0,1} |
{n} | 匹配 n 次,n是非负整数 | zo{2} 匹配 zoo ,不能匹配 zo |
{n,} | 匹配至少 n 次(>=n),n 是一个非负整数 | 。例如,“zo{2,}” 能匹配 zooo,但不能匹配 zo。zo{1,} 等价于 zo 。o{0,} 等价于 zo* |
{n,m} | m和n均为非负整数,其中 n<=m。最少匹配 n 次且最多匹配 m 次 | o{1,3} 将匹配 fooooood 中的前三个o。o{0,1}等价于 o?。请注意在逗号和两个数之间不能有空格 |
? | 当?紧跟在任何一个其他限制符(*, ,?,{n},{n,},{n,m})后面时,匹配模式是懒惰匹配。懒惰模式尽可能少的匹配所搜索的字符串,而默认的贪婪模式则尽可能多的匹配所搜索的字符串 | 对于字符串 oooo,o ? 将匹配每个o,即 4 次匹配,而 o 将只匹配 1 次,即匹配 oooo |
. | 匹配除换行符 rn 之外的任意单个字符。匹配包括换行符在内的任意字符,可使用 [sS] | |
(exp) | 将 () 内的表达式定义为组(group),又称子表达式,并且将匹配这个表达式的字符保存到一个临时区域(一个正则表达式中最多可以保存 9 个),可以用 1 到 9 的符号来引用。要匹配小括号,请使用(或) | |
(?<name>exp) | 匹配 exp 并捕获文本到名称为 name 的组里,也可以写成(?'name'exp)。这个元字符主要作用是给组命名。要反向引用这个分组捕获的内容,你可以使用k<name> | |
(?:exp) | 匹配 exp 但不捕获匹配的文本,也不给此分组分配组号 | 例如 industr(?:y |
(?=exp) | 正向先行零宽断言,断言此位置的后面能匹配表达式 exp,因不消耗字符,所以称为零宽断言 | 比如 industry 能匹配 ind(?=us)ustry,但是不能匹配 ind(?=aa)ustry |
(?<=exp) | 正向后顾零宽断言,断言此位置的前面能匹配表达式 exp | 比如 industry 能够匹配 ind(?<=nd)ustry,但是不能匹配 ind(?<=aa)ustry |
(?!exp) | 反向先行零宽断言,断言此位置的后面不能匹配表达式 exp | 比如 indust(?!aa) 能匹配 industry,但不能匹配 industaa |
(?<!exp) | 反向后顾零宽断言,断言此位置的前面不能匹配表达式 exp | 比如(?<![a-z])d{7}匹配前面不是小写字母的七位数字 |
(?#comment) | 这种类型的分组不对正则表达式的处理产生任何影响,仅提供注释 | |
x|y | 匹配x或y | z|food 能匹配 z 或 food。注意 [z |
[xyz] | 字符集合。匹配所包含的任意一个字符 | [abc] 可以匹配 plain 中的 a |
[^xyz] | 字符补集。匹配指定字符外的任意字符 | [^abc] ” 匹配 plain 中的 pl 和 in |
[a-z] | 字符范围。匹配指定范围内的任意字符 | [a-z] 可以匹配“a”到“z”范围内的任意小写字母字符。注意:只有连字符在字符组内部时,并且出现在两个字符之间时,才能表示字符的范围,如果出现在其它位置,则表示连字符本身 |
[^a-z] | 字符范围补集。匹配不在指定范围内的任意字符 | [^a-z]可以匹配任何不在 a 到 z 范围内的任意字符 |
b | 匹配单词边界,指单词和空格间的位置。正则表达式的“匹配”有两种概念:一种是匹配字符,一种是匹配位置,这里的 b 指匹配位置 | erb 可以匹配 border 中的 er,但不能匹配 verb 中的 er |
B | 匹配非单词边界与 b 功能相反 | erB能匹配 verb 中的 er,但不能匹配 border 中的 er |
<word> | 匹配单词 word 的开始(<)和结束(>)位置,等价于 bwordb | 正则表达式<the>能够匹配字符串"for the wise"中的"the",但是不能匹配字符串"otherwise"中的"the"。注意:该元字符不是所有编程语言都支持 |
d | 匹配一个数字。等价于 [0-9] | |
D | 匹配一个非数字字符。等价于 [^0-9] | |
s | 匹配任意不可打印字符,如空格、制表符、换行符等 | |
S | 匹配任意可打印字符 | |
w | 匹配任意一个组成单词的字符,包括下划线、字母、数字和汉字等 Unicode 字符,类似但不等价于[A-Za-z0-9_] | |
W | 匹配任何非单词字符。类似但不等价于[^A-Za-z0-9_] | |
xnn | 匹配 ASCII 码值为十六进制 nn 的字符 | x41 匹配 A |
num | 匹配 num,其中 num 是一个正整数。表示对前面所获取的子表达式的匹配的引用 | (.)1 匹配两个连续的相同字符 |
oct | 表示一个八进制 ASCII 码值或一个后向引用。如果 oct 之前至少有 oct 个子表达式,则 oct 为后向引用,否则为一个八进制的 ASCII 码值 | |
unnnn | 匹配 Unicode 码值为四个十六进制数字 nnnn 表示的字符 | u00A9 匹配版权符号 © |
| | 逻辑或 | 正则表达式 him|her匹配 “it belongs to him and her” 中的“him”和“her”。注意:该元字符不是所有的编程语言都支持 |
[:lower:] | 匹配任意一个小写字母 | 使用时加上中括号 [],下同,即 [[:lower:]] 等价于 [a-z] |
[:upper:] | 匹配任意一个大写字母 | [[:upper:]] 等价于 [A-Z] |
[:alpha:] | 匹配任意一个字母 | [[:alpha:]] 等价于 [a-zA-Z] |
[:digit:] | 匹配任意一个数字 | [[:digit:]] 等价于 [0-9] |
[:alnum:] | 匹配任意一个字母或数字 | [[:alnum:]] 等价于 [a-zA-Z0-9] |
[:blank:] | 匹配空格或制表符Tab | [[:blank:]] 等价于 [x20t] |
[:space:] | 匹配任意空白字符,包括空格 | [[:space:]] 等价于 [x20trnvf] |
[:graph:] | 匹配任意 ASCII 可见字符 | [[:graph:]] 等价于 [x21-x7E] |
[:print:] | 匹配空格或任意 ASCII 可见字符 | [[:print:]] 等价于 [x20-x7E] |
[:punct:] | 匹配任意标点符号(Punctuation Characters) | [[:punct:]] 等价于[][!"#$%&'()* ,./:;<=>?@^_{|}~-]` |
[:cntrl:] | 匹配任意控制字符 | 比如 CR、LF、Tab、Del 等,[[:cntrl:]] 等价于 [x00-x1Fx7F] |
[:xdigit:] | 匹配任意十六进制数码 | [[:xdigit:]] 等价于 [A-Fa-f0-9] |
以上元字符为日常正则表达式中可能用到的,并未做全部列举。由于不同流派和版本的正则表达式引擎规则有所差异,上述元字符功能并非放之四海而皆准,有些元字符在某些引擎中并未得到支持。
关于上面元字符的描述会涉及到的一些名词概念,会在下面语法一节做详细说明。
语法
- 八进制转义字符
我们学过用一个转义符 加上一个普通字母来表示某个特殊字符的方法,如 n 表示换行符,而 t 表示 Tab 符,'
则表示单引号。八进制转义字符是反斜杠后跟一个八进制数,用于表示 ASCII 码值等于该值的字符。例如问号 ? 的 ASCII 码值是 63,那么我们可以把它转换为八进值 77,然后用 77 来表示 ?。由于是八进制,所以本应写成 77,但因为不允许斜杠加 10 进制数来表示字符,所以这里的 0 可以不写,当然加上 0 也不会错,且易于阅读,所以建议还是加上 0。
同理,十六进制转义字符,就是反斜杠 后面接一个十六进制数来表示一个字符。还是以问号 ?为例,问号 ? 的 ASCII 码值 63 转换为十六进制是 4F,那么它的十六进制转义字符为 x4F。
- 组与反向引用
正则表达式中,使用小括号扩住一个表达式称之为组(group),又称为子表达式,匹配这个子表达式的文本可以在正则表达式或其它程序中作进一步的处理。
默认情况下,每个组会自动拥有一个组号,规则是:从左向右,以组的左括号为标志,第一个出现的组号为 1,第二个为 2,以此类推。反向引用(亦称后向引用)指的是正则表达式重复利用前面某个子表达式。例如:1 代表分组 1 匹配的文本。难以理解?请看示例:
b(w )bs 1b
可以用来匹配重复的单词,像logo logo
或kitty kitty
。这个表达式首先是一个单词,也就是单词开始处和结束处之间存在多于一个字母或数字b(w )b
,这个单词会被捕获到编号为 1 的组中,然后是 1 个或几个空白符s
,最后是组 1 中捕获的内容(也就是前面匹配的那个单词)。
- 零宽断言
零宽断言(Zero Width Assertion),是一种零宽度的匹配,它匹配到的内容不会保存到匹配结果中去,因不会消耗待匹配字符,所以有“零宽度”之说。又因像元字符b、^、$那样用于指定一个位置,该位置应该满足一定的条件(即断言),所以称之为零宽断言。零宽断言根据是否匹配表达式 exp 分为正向与负向,匹配则为正向零宽断言(Positive Zero Width Assertion),不匹配则为负向零宽断言(Negative Zero Width Assertion)。
正向零宽断言根据匹配的方向分为两种,从当前位置向右匹配,为正向先行零宽断言(Positive Lookahead Zero Width Assertion),使用元字符(?=exp)
表示;从当前位置向左匹配,为正向后顾零宽断言(Positive Lookbehind Zero Width Assertion),使用元字符(?<=exp)
表示。上文已有简单的举例说明,分别再看一下例子说明。
正向先行零宽断言的例子。比如bw (?=ingb)
,匹配以 ing 结尾的单词的前面部分(除了 ing 以外的部分),如查找I’m singing while you’re dancing.时,它会匹配 sing 和 danc。再来个正向后顾零宽断言的例子,比如(?<=bre)w b
会匹配以 re 开头单词的后半部分(不包含 re),例如在查找 reading a book 时,它匹配 ading。
负向零宽断言根据匹配的方向同样分为两种,从当前位置向右匹配,为负向先行零宽断言(Negative Lookahead Zero Width Assertion),使用元字符(?!exp)
表示;从当前位置向左匹配,为负向后顾零宽断言(Negative Lookbehind Zero Width Assertion),使用元字符(?<!exp)
表示。上文已有简单的举例说明,分别再看一下例子说明。
看一个负向先行零宽断言的例子,例如d{3}(?!d)
匹配三位数字,而且这三位数字的后面不能是数字。再看一个负向后顾零宽断言,例如b(?!abc)w b
匹配不以字符串 abc 开头的单词。
- 懒惰与贪婪匹配
当正则表达式中包含能接受重复的限定符时,通常的行为是(在使整个表达式能得到匹配的前提下)匹配尽可能多的字符。例如表达式 a.*b,它将会匹配最长的以 a 开始,以 b 结束的字符串。如果用它来搜索 aabab 的话,它会匹配整个字符串 aabab。这被称为贪婪匹配。
有时,我们更需要懒惰匹配,也就是匹配尽可能少的字符。前面给出的限定符都可以被转化为懒惰匹配模式,只要在它后面加上一个问号 ?。这样 .*? 就意味着匹配任意数量的重复,但是在能使整个匹配成功的前提下使用最少的重复。
现在看看懒惰版的例子。a.*?b 匹配最短的以 a 开始,以 b 结束的字符串。如果把它应用于 aabab 的话,它会匹配aab(第一到第三个字符)和 ab(第四到第五个字符)。
运算符优先级
正则表达式从左到右进行计算,并遵循优先级顺序,这与算术表达式非常类似。
相同优先级的从左到右进行运算,不同优先级的运算先高后低。下表从最高到最低说明了各种正则表达式运算符的优先级顺序。
优先级 | 运算符 | 描述 |
---|---|---|
p0 | 转义符 | |
p1 | (), (?:), (?=), [] | 圆括号和方括号 |
p2 | *, , ?, {n}, {n,}, {n,m} | 限定符 |
p3 | ^, $, 任何元字符、任何字符 | 定位点和字符 |
p4 | | | 逻辑或 |
常用示例
- 匹配身份证号码。身份证号为 18 位,前 17 位为数字,最后一位为数字或者大写字母 X。
^d{17}([0-9]|X)$
- 匹配中国国内电话号码,形式如 0511-4405222 或 021-87888822。
`d{3}-d{8}|d{4}-d{7}`
- 匹配中国邮政编码。中国邮政编码为 6 位数字,第一位不为零。
^[1-9]d{5}$
- 匹配腾讯 QQ 号。腾讯 QQ 号从 10000 开始,最长为 10 位,10000 为腾讯官方 QQ,10001 为马化腾的 QQ。
[1-9][0-9]{4,}
- 匹配 Email 地址。
w ([- .]w )*@w ([-.]w )*.w ([-.]w )*
- 匹配网址 URL。
[a-zA-z] ://[^S]*`
- 匹配合法帐号。假设账号以字母开头,5-16 个字符,只允许字母数字下划线。
^[a-zA-Z][a-zA-Z0-9_]{4,15}$
- 匹配 IP 地址。IPv4 的地址的格式是
(0~255).(0~255).(0~255).(0~255)
。
^((25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9]?[0-9]).){3}(25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9]?[0-9])$
- 匹配使用 UTF8 编码的中文。
[u4e00-u9fa5]
- 匹配特定数字。
^[1-9]d*$ //匹配正整数
^-[1-9]d*$ //匹配负整数
^-?[1-9]d*$ //匹配整数
^[1-9]d*|0$ //匹配非负整数(正整数 0)
^-[1-9]d*|0$ //匹配非正整数(负整数 0)
^[1-9]d*.d*|0.d*[1-9]d*$ //匹配正浮点数
^-([1-9]d*.d*|0.d*[1-9]d*)$ //匹配负浮点数
^-?([1-9]d*.d*|0.d*[1-9]d*|0?.0 |0)$ //匹配浮点数
^[1-9]d*.d*|0.d*[1-9]d*|0?.0 |0$ //匹配非负浮点数(正浮点数 0)
^(-([1-9]d*.d*|0.d*[1-9]d*))|0?.0 |0$ //匹配非正浮点数(负浮点数 0)
- 匹配特定字符串。
^[A-Za-z] $ //匹配由26个英文字母组成的字符串
^[A-Z] $ //匹配由26个英文字母的大写组成的字符串
^[a-z] $ //匹配由26个英文字母的小写组成的字符串
^[A-Za-z0-9] $ //匹配由数字和26个英文字母组成的字符串
^w $ //匹配由数字、26个英文字母或者下划线组成的字符串
- 匹配空行。
windows: ^(s*)rn
linux: ^(s*)n
mac: ^(s*)r
- 匹配任意字符串,包括不可打印字符。
[sS]
//或
[sS]{1,}