字符
元字符:$()*+.?[\^{|
。注意这个列表中并不包含右括号 ]
、}
和连字符-
。前两个符号只有在它们位于一个没有转移的 [
和 {
之后才成为元字符,在任何时候都没有必要对 }
进行转义。
\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中会忽略所有空白符,因此 [ ]
方法无效。