一、转义
元字符是一些在正则表达式里有着特殊含义的字符。英文句号 . 是一个元字符,它可以用来匹配任意单个字符。类似地,左方括号 [ 也是一个元字符,它标志着一个字符集合的开始。因为元字符在正则表达式里有着特殊的含义,所以这些字符就无法用来代表它们本身。例如不能使用 [ 来匹配 [ 本身,也不能使用 . 来匹配 . 本身。来看一个例子,用一个正则表达式去匹配一个包含 [ 和 ] 字符的 JavaScript 数组。
代码语言:javascript复制mysql> set @s:='var myArray = new Array();
'> ...
'> if (myArray[0] == 0) {
'> ...
'> }';
Query OK, 0 rows affected (0.00 sec)
mysql> set @r:='myArray[0]';
Query OK, 0 rows affected (0.00 sec)
mysql> select regexp_extract(@s, @r, ''), regexp_extract_index(@s, @r, 0, '');
---------------------------- -------------------------------------
| regexp_extract(@s, @r, '') | regexp_extract_index(@s, @r, 0, '') |
---------------------------- -------------------------------------
| | NULL |
---------------------------- -------------------------------------
1 row in set (0.00 sec)
在这个例子里,原始文本是一段 JavaScript 代码。因为 [ 和 ] 在正则表达式里是用来定义一个字符集合,而不是 [ 和 ] 本身的元字符。所以,myArray[0] 匹配的是 myArray 后面跟着一个该集合成员的情况,而那个集合只有一个成员 0。因此,myArray[0] 只能匹配到 myArray0。
在元字符的前面加上一个反斜杠就可以对它进行转义。因此,. 匹配.,[ 匹配[。每个元字符都可以通过在前面加上一个反斜杠的方法来转义,这样匹配的就是该字符本身而不是其特殊的元字符含义。要想匹配 [ 和 ],就必须对这两个字符进行转义。下面的例子与刚才的问题完全一样,但这次对正则表达式里的元字符都进行了转义。
代码语言:javascript复制mysql> set @r:='myArray\[0\]';
Query OK, 0 rows affected (0.00 sec)
mysql> select regexp_extract(@s, @r, ''), regexp_extract_index(@s, @r, 0, '');
---------------------------- -------------------------------------
| regexp_extract(@s, @r, '') | regexp_extract_index(@s, @r, 0, '') |
---------------------------- -------------------------------------
| myArray[0] | 36 |
---------------------------- -------------------------------------
1 row in set (0.00 sec)
这次搜索取得了预期的结果。[ 匹配 [,] 匹配 ],所以
代码语言:javascript复制myArray[0]
匹配到了 myArray[0]。在这个例子中使用正则表达式多少有些大材小用了,因为一个简单的文本匹配操作已足以完成这一任务,而且还更容易。但如果你想查找的不仅仅是 myArray[0],还包括 myArray[1]、myArray[2] 等,正则表达式就能派上用场了。具体做法是,对 [ 和 ] 进行转义,再在两者之间列出要匹配的字符。如果想匹配数组元素 0 到 9,那么构造出来的正则表达式应该是下面这个样子:
代码语言:javascript复制myArray[[0-9]]
对元字符进行转义需要用到 字符。这意味着 字符也是一个元字符,它的特殊含义是对其他元字符进行转义。在需要匹配 本身的时候,必须把它转义为 \。看看下面这个简单的例子。例子中的文本是一个包含反斜杠字符的文件路径(用于 Windows 系统)。假设想在一个 Linux 系统上使用这个路径,也就是说,需要把这个路径里的反斜杠字符 全部替换为斜杠字符 /。
代码语言:javascript复制mysql> set @s:='\home\ben\sales\';
Query OK, 0 rows affected (0.00 sec)
mysql> set @r:='\\';
Query OK, 0 rows affected (0.00 sec)
mysql> select @s, regexp_replace(@s,@r,'/');
------------------ ---------------------------
| @s | regexp_replace(@s,@r,'/') |
------------------ ---------------------------
| homebensales | /home/ben/sales/ |
------------------ ---------------------------
1 row in set (0.00 sec)
\ 匹配 ,总共找到了 4 处匹配。在正则表达式中,字符 的后面总是跟着另一个字符。
二、匹配空白字符
元字符大致可以分为两种:一种是用来匹配文本的,比如 .,另一种是正则表达式语法的组成部分,比如 [ 和 ]。随着学习的深入,将发现越来越多的这两种元字符,而现在要介绍的是一些用来匹配空白字符的元字符。在进行正则表达式搜索的时候,经常会需要匹配文本中的非打印空白字符。比如可能想把所有的制表符或换行符找出来。直接在正则表达式中输入这类字符是件棘手的事,可以借助下表中列出的特殊元字符。
元字符 | 说明 |
---|---|
[b] | 回退并删除一个字符(Backspace键) |
f | 换页符 |
n | 换行符 |
r | 回车符 |
t | 制表符(Tab键) |
v | 垂直制表符 |
来看一个例子。下面的文本中包含一些以逗号分隔的数据记录(通常称为 CSV)。在进一步处理这些记录之前,得先把夹杂在这些数据里的空白行去掉。
代码语言:javascript复制mysql> set @s:='"101","Ben","Forta"
'> "102","Jim","James"
'>
'> "103","Roberta","Robertson"
'> "104","Bob","Bobson"';
Query OK, 0 rows affected (0.00 sec)
mysql> set @r:='\n\n';
Query OK, 0 rows affected (0.00 sec)
mysql> select @r, regexp_replace(@s,@r,'n');
------ ------------------------------------------------------------------------------------------
| @r | regexp_replace(@s,@r,'n') |
------ ------------------------------------------------------------------------------------------
| nn | "101","Ben","Forta"
"102","Jim","James"
"103","Roberta","Robertson"
"104","Bob","Bobson" |
------ ------------------------------------------------------------------------------------------
1 row in set (0.00 sec)
n 匹配换行符(line feed)。因此,搜索 nn 将匹配两个连续的行尾标记,而这正是两条记录之间的空白行。
一般来说,需要匹配 r、n 和 t(制表符)等空白字符的情况比较多见,而需要匹配其他空白字符的情况要相对少一些。. 和 [ 是元字符,但前提是没有对它们进行转义。只有对 f 和 n 转义后,它们才是元字符。否则,两者只是普通字符,只能匹配它们本身。
三、匹配特定的字符类型
字符集合(匹配一组字符中的某一个)是最常见的匹配形式,而一些常用的字符集合可以用特殊元字符来代替。这些元字符匹配的是某一类字符。类元字符(class metacharacter)并不是必不可少的东西(总是可以通过逐一列举有关字符或是通过定义一个字符区间来实现相同的效果),但它们在实践中极其有用。
下面讲解的都是一些最基本的字符类,几乎所有的正则表达式实现都支持它们。
1. 匹配数字(d)与非数字(D)
d 等价于 [0-9];D 等价于 [^0-9]。
代码语言:javascript复制mysql> set @s:='var myArray = new Array();
'> ...
'> if (myArray[0] == 0) {
'> ...
'> }';
Query OK, 0 rows affected (0.00 sec)
mysql> set @r:='myArray\[\d\]';
Query OK, 0 rows affected (0.00 sec)
mysql> select @r, regexp_extract(@s, @r, ''), regexp_extract_index(@s, @r, 0, '');
--------------- ---------------------------- -------------------------------------
| @r | regexp_extract(@s, @r, '') | regexp_extract_index(@s, @r, 0, '') |
--------------- ---------------------------- -------------------------------------
| myArray[d] | myArray[0] | 36 |
--------------- ---------------------------- -------------------------------------
1 row in set (0.00 sec)
代码语言:javascript复制 [ 匹配 [,d 匹配任意单个数字字符,] 匹配 ],所以 myArray[d] 匹配 myArray[0]。myArray[d] 是 myArray[[0-9]] 的简写形式,而后者又是 myArray[[0123456789]] 的简写形式。这个正则表达式还可以匹配 myArray[1]、myArray[2],等等,但不包括myArray[10]。
正则表达式的语法是区分字母大小写的。d 匹配数字,D 与 d 的含义刚好相反,只匹配非数字。接下来将看到的其他类元字符也是如此。即便是执行不区分字母大小写的匹配,也是如此。在这种情况下,匹配到的文本不区分字母大小写,但特殊字符(比如 d)会区分。
2. 匹配字母数字(w)与非字母数字(W)
w 等价于 [a-zA-Z0-9_];W 等价于 [^a-zA-Z0-9_]。
代码语言:javascript复制mysql> set @s:='11213
'> A1C2E3
'> 48075
'> 48237
'> M1B4F2
'> 90046
'> H1H2H2';
Query OK, 0 rows affected (0.00 sec)
mysql> set @r:='\w\d\w\d\w\d';
Query OK, 0 rows affected (0.00 sec)
mysql> select @r, regexp_extract(@s, @r, ''), regexp_extract_index(@s, @r, 0, '');
-------------- ---------------------------- -------------------------------------
| @r | regexp_extract(@s, @r, '') | regexp_extract_index(@s, @r, 0, '') |
-------------- ---------------------------- -------------------------------------
| wdwdwd | A1C2E3,M1B4F2,H1H2H2 | 7,26,39 |
-------------- ---------------------------- -------------------------------------
1 row in set (0.00 sec)
在这个模式里,交替出现的 w 和 d 元字符使得匹配结果里只包含加拿大城市的邮政编码。注意,正则表达式很少有对错之分(当然,前提是它们能解决问题)。更多的时候,正则表达式的复杂程度取决于模式匹配的严格程度。
3. 匹配空白字符(s)与非空白字符(S)
s 等价于 [fnrtv];S 等价于 [^fnrtv]。
注意,用来匹配退格字符的 [b] 元字符不在 s 的覆盖范围内,S 也没有将其排除。
4. 匹配十六进制或八进制数值
尽管可能不需要通过十六进制或八进制值来引用某个字符,但要指出的是,这么做是可以的。使用十六进制值在正则表达式里,十六进制值(基数为16)要用前缀 x 来给出。比如 x0A 对应于 ASCII 10 的字符,也就是换行符,等价于n。 使用八进制值在正则表达式里,八进制值(基数为8)要用前缀 来给出,数值本身可以是两位或三位数字。比如 11 对应于 ASCII 9 的字符,也就是制表符,等价于 t。注意,有不少正则表达式实现还允许使用 c 前缀来指定各种控制字符。比如 cZ 可以匹配 Ctrl-Z。不过在实践中极少会用到这种语法。
四、使用POSIX字符类
对元字符以及各种字符集合进行的讨论,必须要提到 POSIX 字符类。POSIX 是一种特殊的标准字符类集,也是许多(但不是所有)正则表达式实现都支持的一种简写形式。下表列出了 12 个 POSIX 字符类。
字符类 | 说明 |
---|---|
[:alnum:] | 任何一个字母或数字,等价于[a-zA-Z0-9] |
[:alpha:] | 任何一个字母,等价于[a-zA-Z] |
[:blank:] | 空格或制表符,等价于[t ] |
[:cntrl:] | ASCII控制字符,ASCII 0-31,再加上ASCII 127 |
[:digit:] | 任何一个数字,等价于[0-9] |
[:graph:] | 和[:print:]一样,但不包括空格 |
[:lower:] | 任何一个小写字母,等价于[a-z] |
[:print:] | 任何一个可打印字符 |
[:punct:] | 既不属于[:alnum:],也不属于[:cntrl:]的任何一个字符 |
[:space:] | 任何一个空白符,包括空格,等价于[fnrtv ] |
[:upper:] | 任何一个大写字母,等价于[A-Z] |
[:xdigit:] | 任何一个十六进制数字,等价于[a-fA-F0-9] |
看一个例子——利用正则表达式从一段 HTML 代码里把 RGB 值查找出来。
代码语言:javascript复制mysql> set @s:='body {
'> background-color: #fefbd8;
'> }
'> h1 {
'> background-color: #0000ff;
'> }
'> div {
'> background-color: #d0f4e6;
'> }
'> span {
'> background-color: #f08970;
'> }';
Query OK, 0 rows affected (0.00 sec)
mysql> set @r:='#[[:xdigit:]][[:xdigit:]][[:xdigit:]][[:xdigit:]][[:xdigit:]][[:xdigit:]]';
Query OK, 0 rows affected (0.00 sec)
mysql> select regexp_extract(@s, @r, ''), regexp_extract_index(@s, @r, 0, '');
--------------------------------- -------------------------------------
| regexp_extract(@s, @r, '') | regexp_extract_index(@s, @r, 0, '') |
--------------------------------- -------------------------------------
| #fefbd8,#0000ff,#d0f4e6,#f08970 | 30,68,107,147 |
--------------------------------- -------------------------------------
1 row in set (0.00 sec)
这里使用的模式以 [[ 开头、以 ]] 结束(两对方括号)。这是使用 POSIX 字符类所必需的,这点很重要。POSIX 字符类必须出现在 [: 和 :] 之间,外层的 [ 和 ] 字符用来定义一个字符集合,内层的 [ 和 ] 字符是 POSIX 字符类本身的组成部分。一般来说,支持 POSIX 标准的正则表达式实现都支持上表所列出的 POSIX 字符类,但在一些细节方面可能与之前的描述有细微的差异。