字符
元字符:$()*+.?[\^{|。注意这个列表中并不包含右括号 ]、} 和连字符-。前两个符号只有在它们位于一个没有转移的 [ 和 { 之后才成为元字符,在任何时候都没有必要对 } 进行转义。
\Q 会抑制所有元字符的含义,直到 \E,如 \Q!"#$%^&*()-+\E。
模式修饰符 (?i) 设置后面部分不区分大小写,直到 (?-i)(不是所有语言都支持,如 JS 是 /.../i)。这样的模式切换不会影响到捕获组。也可以使用 abc(?ism:abc|def)ABC、(?i-sm)这样的格式。
单行模式下,. 匹配换行符外的任意字符,[\s\S] 匹配包含换行符在内的任意字符,类似的 [\w\W] 也有同样效果。部分语言使用 (?s) 开启点号匹配换行符。
^ $ \A \Z 匹配一行的首尾,前两者在开启“^和$匹配换行处(多行模式)”后可以匹配换行,一般默认关闭,Ruby强制开启,JS 不支持 \Z \z。$ 和 \Z 可以匹配最后一个换行符之前的位置(即可忽略最后一个换行符),\z 则只匹配目标文本最末尾处。
\b 只在单词字符边界有效,如 x\bx 和 !\b! 永远不会匹配任何位置。如果换行符紧跟在一个单词字符后面,那么\b会匹配换行符后面的位置,它同样也会匹配单词字符之前的换行符,这样一个占据了一整行的单词也可以通过一个“只匹配完整单词”的查找来发现。
还没参与匹配的分组,并不等同于捕获到长度为 0 的分组,如 \1(\d) 永远不会成功(JS除外),但 (^)\1 则总是成功。
流派相关特性
.NET 字符差:[a-zA-Z0-9-[g-zG-Z]] 表示一个不是 g~z 的字符或数字,采用嵌套类的方式,嵌套类必须出现在基类的最后,紧跟在一个连字符后面:[class-[subclass]]。[\p{IsThai}-[\P{N}]] 匹配任意的10个泰语数字字符。
Java 并集(union):[a-f[A-F][0-9]],交集(intersection):[\w&&[a-fA-F0-9\s]] 相当于 [a-zA-Z0-9&&[^g-zG-Z]],匹配十六进制数字。如果 subclass 是否定类,则是作差,即 [class&&[^substract]],如[\p{InThai}&&[\P{N}]] 匹配任意的10个泰语数字字符。
| 元字符 | 含义 |
|---|---|
\p{L} |
所有字母 |
\p{N} |
所有数字,类似于 \d |
[\p{N}\p{L}] |
所有数字和所有字母,类似于 \w |
\P{L} |
不是字母,等价于 [^\p{L}] |
\P{N} |
不是数字,等价于 [^\p{N}] |
分组
使用 \1、\2 来捕获 () 包含的分组
命名捕获:(?P<name>regex)(Python)、(?<name>regex)、(?'name'regex)(.NET)
命名反向应用:(?P=name)、\k<name>、\k'name'
条件判断
使用 (?(1)then|else) 或 (?(name)then|else)(命名仅 .NET 支持) 来判断指定分组有没有产生匹配(匹配空字符串也算产生匹配),其中 then 或 else 可为空(相当于长度为 0 的匹配)。如果里面想要使用多选结构,需要用分组包起来,条件判断中只允许直接出现一个竖线。
Java 和 Ruby 不支持条件判断
示例:匹配字符串中右逗号分隔的 one two three 三个单词,且每个单词至少出现一次:
1 | \b(?:(?:(one)|(two)|(three))(?:,|\b)){3,}(?(1)|(?!))(?(2)|(?!))(?(3)|(?!))) |
一个空的否定型顺序环视 (?!) 被用在了 else 部分,因为空的正则表达式总是会产生匹配,所有包含空正则表达式的否定型顺序环视则总是会匹配失败。因此,当第一个捕获分组没有匹配到任何东西的时候,条件判断 (?(1)|(?!)) 总是会失败。
示例:(a)?b(?(1)c|d) 等价于 abc|bd。如果是 (a?),则总是会匹配成功。
在 .NET、PCRE、Perl 中,条件判断还可以使用环视:(?(?=if)then|else)。不推荐使用否定型环视,因为它会把 then 和 else 的人含义反转。
懒惰模式
懒惰模式并不会影响匹配的成功和失败。
贪心量词?:\d+?\w\b 能匹配到 1234X,结果是 1234X 而不是 4X
占有量词:永远不会回退,*+、++、?+、{7,42}+
\w++\d++ 和 (?>\w+)(?>\d+) 一样,和 (?>\w+\d+) 不同,后者有两个贪心量词,回溯的位置只有当引擎退出整个分组的时候才会被丢弃
明确消除不必要的回溯
\b\d+\b、\b\d+?\b、\b\d++\b、\b(?>\d+)\b 都会匹配一个整数。在匹配“123abc 456” 时,前两者在 123a 处匹配失败,会没必要地回溯尝试 23a、3a 并依旧失败;而后两者占有量词会把这些回溯的位置丢弃,往后去匹配 456。
匹配HTML标签
使用一个正则表达式来匹配一个完整的 HTML 文件,并检查其中的几个标签是否进行了正确的嵌套。(打开多行模式和忽略大小写)
1 | <html>.*?<head>.*?<title>.*?</title>.*?</head>.*?<body[^>]*>.*?</body>.*?</html> |
当一个正确的 HTML 文件上测试的时候,它会完全正常地运行。最坏的情况是当 </html> 缺失的时候,最后一个 .*? 以及另外 6 个 .*? 都记住了一个回溯位置,逐步匹配并逐渐扩展到文件结尾,直到无法再进行扩展,此时复杂度是 O(n^7^),这种情形称作灾难性回溯,会毁掉程序的性能。
优化后的版本:
1 | <html>(?>.*?<head>)(?>.*?<title>)(?>.*?</title>)(?>.*?</head>)(?>.*?<body[^>]*>)(?>.*?</body>).*?</html> |
环视
逆序环视(前):
(?<=)、(?<!),不支持无限长度量词(*、+、{})一些引擎(JS、Ruby1.8)不支持逆序环视,且效率一般,建议使用捕获组或者匹配两次代替
顺序环视(后):
(?=)、(?!)
慎用环视
(?=(\d+))\w+\1无法匹配123x12(?=\d+)会首先匹配123,存到第一个捕获分组中,并丢弃由贪心的加号所记住的回溯位置。\w+会贪心地匹配123x12,发现失败于是挨个回退,直到最后第一个 1 并同样失败。如果正则引擎能返回到顺序环视中,放弃 123 而选择 12,那么则可以匹配,但它并不会这么做。
\w+已经回退到头了,但\d+把它的回溯位置丢掉了,因此匹配宣告失败。(?<=(\d+))\w+\1匹配123x12结果:(23x1, 1)而不是(3x12, 12)可能因为环视里面的 + * 不是贪婪的,表达式末尾加上 $ 则可以匹配全部
注释
宽松排列模式
1 | (\d{4}) #year |
1 | (?#Year)\d{4}-(?#Month)\d{2}-(?#Day)\d{2} |
此模式会忽略所有空白符和 #,若要使用空格得用以下方法替换:
- 字符类
[ ]、[#] - 转义
\、\# \x20\u0020\x{0020}
Java / JavaScript中会忽略所有空白符,因此 [ ] 方法无效。