《正则表达式必知必会》——Ben Forta,回顾笔记。
正则表达式的两种基本用途:搜索和替换。给定一个正则表达式,它要么匹配一些文本(进行一次搜索),要么匹配并替换一些文本(进行一次替换)。
正则表达式的核心难点:验证某个模式能不能获得预期的匹配结果并不困难,但如何验证它不会匹配到你不想要的东西可就没那么简单了。
利用字符集合区间
字符区间的首、尾字符可以是ASCII字符表里的任意字符。但在实际工作中,最常用的字符区间还是数字字符区间和字母字符区间:
- ❑ A-Z,匹配从A到Z的所有大写字母。
- ❑ a-z,匹配从a到z的所有小写字母。
- ❑ A-F,匹配从A到F的所有大写字母。
- ❑ A-z,匹配从ASCII字符A到ASCII字符z的所有字母。这个模式一般不常用,因为它还包含着 [ 和 ^ 等在ASCII字符表里排列在Z和a之间的字符。
注意 -(连字符)是一个特殊的元字符,作为元字符它只能用在[和]之间。在字符集合以外的地方,-只是一个普通字符,只能与-本身相匹配。因此,在正则表达式里,-字符不需要被转义。
取非匹配
用元字符^来表明你想对一个字符集合进行取非匹配——这与逻辑非运算很相似,只是这里的操作数是字符集合而已。
^的效果将作用于给定字符集合里的所有字符或字符区间,而不是仅限于紧跟在^字符后面的那一个字符或字符区间。
匹配特定的字符类别
元字符 | 说明 |
---|---|
\s | 任何一个空白字符(等价于[\f\n\r\t\v]) |
\S | 任何一个非空白字符(等价于[\f\n\r\t\v]) |
\d | 任何一个数字字符(等价于[O-9]) |
\D | 任何一个非数字字符(等价于[^0-9]) |
\w | 任何一个字母数字字符(大小写均可)或下划线字符(等价于[a-zA-Z0-9_]) |
\W | 任何一个非字母数字或非下划线字符(等价于[^a-zA-Z0-9]) |
用来匹配退格字符的[\b]元字符是一个特例:它不在类元字符\s的覆盖范围内,当然也就没有被排除在类元字符\S的覆盖范围外。
使用POSIX字符类
POSIX字符类是许多(但不是所有)正则表达式实现都支持的一种简写形式。
注意 这里使用的模式以 [[ 开头、以 ]] 结束(两对方括号)。这是使用POSIX字符类所必须的。POSIX字符类必须括在 [: 和 :] 之间,我们使用的POSIX字符类是 [:xdigit:](不是:xdigit:)。外层的 [ 和 ] 字符用来定义一个字符集合,内层的 [ 和 ] 字符是 POSIX 字符类本身的组成部分。
重复匹配
元字符 | 说明 |
---|---|
+ | 匹配字符或字符集合的一次或多次重复出现 |
* | 匹配字符或字符集合的零次或多次重复出现 |
? | 匹配字符或字符集合的零次或一次出现 |
{3} | 匹配字符或字符集合的3次 |
{3, 10} | 匹配字符或字符集合的 3 次到 10 次之间 |
{3, } | 匹配字符或字符集合的 3 次到无数次 |
一般来说,当在字符集合里使用的时候,像 . 和 + 这样的元字符将被解释为普通字符,不需要被转义——但转义了也没有坏处。[\w.] 的使用效果与 [\w.] 是一样的。
[ ]的常规用法是把多个字符定义为一个集合,但有不少程序员喜欢把一个字符也定义为一个集合。这么做的好处是可以增加可读性和避免产生误解,让人们一眼就可以看出哪个字符与哪个元字符相关联。
因为*和+都是所谓的“贪婪型”元字符,它们在进行匹配时的行为模式是多多益善而不是适可而止的。它们会尽可能地从一段文本的开头一直匹配到这段文本的末尾,而不是从这段文本的开头匹配到碰到第一个匹配时为止。
在不需要这种“贪婪行为”的时候,可以使用这些元字符的“懒惰型”版本。懒惰型元字符的写法很简单,只要给贪婪型元字符加上一个?后缀即可。
贪婪型 | 堕落型 |
---|---|
+ | +? |
* | *? |
{3, } | {3, }? |
匹配边界
\b到底匹配什么东西呢?正则表达式引擎不懂任何人类语言,也不知道什么是单词边界。简单地说,\b匹配的是一个这样的位置,这个位置位于一个能够用来构成单词的字符(字母、数字和下划线,也就是与\w相匹配的字符)和一个不能用来构成单词的字符(也就是与\W相匹配的字符)之间。
如果你想表明不匹配一个单词边界,请使用\B。
单词边界可以用来进行与单词有关的位置匹配(单词的开头、单词的结束、整个单词,等等)。字符串边界有着类似的用途,只不过是用来进行与字符串有关的位置匹配而已(字符串的开头、字符串的结束、整个字符串,等等)。用来定义字符串边界的元字符有两个:一个是用来定义字符串开头的^,另一个是用来定义字符串结尾的$。
有许多正则表达式都支持使用一些特殊的元字符去改变另外一些元字符行为的做法,用来启用分行匹配模式(multiline mode)的(? m)记号就是一个能够改变其他元字符行为的元字符序列。
子表达式
子表达式是一个更大的表达式的一部分;把一个表达式划分为一系列子表达式的目的是为了把那些子表达式当作一个独立元素来使用。
子表达式的作用是把同一个表达式的各个相关部分组合在一起。子表达式必须用(和)来定义。子表达式的常见用途包括:对重复次数元字符的作用对象做出精确的设定和控制、对|操作符的OR条件做出准确的定义,等等。
如有必要,子表达式允许嵌套。事实上,子表达式允许多重嵌套,这种嵌套的层次在理论上没有限制,但在实际工作中还是应该遵循适可而止的原则。
nbsp是“non-breaking space”的缩写,其含义是“不是换行符的空格”
回溯引用(backreference)
回溯引用指的是模式的后半部分引用在前半部分中定义的子表达式。可以把回溯引用想像成变量。
回溯引用只能用来引用模式里的子表达式(用(和)括起来的正则表达式片段)。
回溯引用匹配通常从1开始计数(\1、\2,等等)。在许多实现里,第0个匹配(\0)可以用来代表整个正则表达式。
但这种语法存在着一个严重的不足:如果子表达式的相对位置发生了变化,整个模式也许就不能再完成原来的工作,删除或添加子表达式的后果可能更为严重。为了弥补这一不足,一些比较新的正则表达式实现还支持“命名捕获”(named capture):给某个子表达式起一个唯一的名字,然后用这个名字(而不是相对位置)来引用这个子表达式。
有些正则表达式实现允许我们对字母进行大小写转换。
元字符 | 说明 |
---|---|
\E | 结束\L或\U转换 |
\l | 把下一个字符转换为小写 |
\L | 把\L到\E之间的字符全部转换为小写 |
\u | 把下一个字符转换为大写 |
\U | 把\U到\E之间的字符全部转换为大写 |
前后查找(lookaround)
前后查找(lookaround,对某一位置的前、后内容进行查找)
有时候,你需要的是这样一个模式:它包含的匹配本身并不返回,而是用于确定正确的匹配位置,它并不是匹配结果的一部分。换句话说,你需要进行“前后查找”。
向前查找
向前查找指定了一个必须匹配但不在结果中返回的模式。向前查找实际就是一个子表达式,而且从格式上看也确实如此。从语法上看,一个向前查找模式其实就是一个以 ?= 开头的子表达式,需要匹配的文本跟在 = 的后面。任何一个子表达式都可以转换为一个向前查找表达式,只要给它加上一个 ?= 前缀即可。在同一个搜索模式里可以使用多个向前查找表达式,它们可以出现在模式里的任意位置。
有些正则表达式文档使用术语“消费”(consume)来表述“匹配和返回文本”的含义。在向前查找里,被匹配的文本不包含在最终返回的匹配结果里,这被称为“不消费”。
前后查找操作有时也被称为零宽度(zero-width)匹配操作。
向后查找
除了向前查找,许多正则表达式实现还支持向后查找,也就是查找出现在被匹配文本之前的字符(但不消费它),向后查找操作符是 ?<= 。
向前查找模式的长度是可变的,它们可以包含.和+之类的元字符,所以它们非常灵活。而向后查找模式只能是固定长度——这是一条几乎所有的正则表达式实现都遵守的限制。
向前查找和向后查找通常用来匹配文本,其目的是为了确定将被返回为匹配结果的文本的位置(通过指定匹配结果的前后必须是哪些文本)。这种用法被称为正向前查找(positive lookahead)和正向后查找(positive lookbehind)。术语“正”指的是寻找匹配的事实。
前后查找还有一种不太常见的用法叫做负前后查找(negative lookaround)。负向前查找(negative lookahead)将向前查找不与给定模式相匹配的文本,负向后查找(negative lookbehind)将向后查找不与给定模式相匹配的文本。
前后查找必须用 ! 来取非(它将替换掉=)。
操作符 | 说明 |
---|---|
(?=) | 正向前查找 |
(?!) | 负向前查找 |
(?<=) | 正向后查找 |
(?<!) | 负向后查找 |
正则表达式里的条件
在正则表达式模式里可以嵌入条件,只有当条件得到(或者没有得到)满足时,相应的表达式才会被执行。嵌入条件不外乎以下两种情况:
- ❑ 根据一个回溯引用来进行条件处理。
- ❑ 根据一个前后查找来进行条件处理。
嵌入条件语法也使用了 ? 元字符。
回溯引用条件只在一个前面的子表达式搜索取得成功的情况下才允许使用一个表达式。
前后查找条件前后查找条件只在一个向前查找或向后查找操作取得成功的情况下才允许一个表达式被使用。定义一个前后查找条件的语法与定义一个回溯引用条件的语法大同小异——只需把回溯引用(括号里的回溯引用编号)替换为一个完整的前后查找表达式就行了。