正则表达式必知必会 - 位置匹配

目录

一、边界

二、单词边界

三、字符串边界


一、边界

        位置匹配用于指定应该在文本中什么地方进行匹配操作,先来看一个例子。

mysql> set @s:='The cat scattered his food all over the room.';
Query OK, 0 rows affected (0.00 sec)

mysql> set @r:='cat';
Query OK, 0 rows affected (0.00 sec)

mysql> select regexp_extract(@s, @r, '') s, regexp_extract_index(@s, @r, 0, '') i;
+---------+------+
| s       | i    |
+---------+------+
| cat,cat | 5,10 |
+---------+------+
1 row in set (0.00 sec)

        模式 cat 可以匹配文本里所有的 cat,即便是单词 scattered 里的那个 cat 也不例外。但这很可能并不是想要的结果。如果这样搜索所有的cat,并将其替换为dog,那么得到的只会是毫无实际意义的一句话。这就要用到边界了,也就是一些用于指定模式前后位置(或边界)的特殊元字符。

二、单词边界

        第一种边界,也是最常用到的,是由 \b 指定的单词边界。b 是英文 boundary 的首字母,顾名思义,\b 用来匹配一个单词的开头或结尾。

mysql> set @r:='\\bcat\\b';
Query OK, 0 rows affected (0.00 sec)

mysql> select regexp_extract(@s, @r, '') s, regexp_extract_index(@s, @r, 0, '') i;
+------+------+
| s    | i    |
+------+------+
| cat  | 5    |
+------+------+
1 row in set (0.00 sec)

        单词 cat 的前后都有一个空格,所以匹配模式 \bcat\b,空格是用来分隔单词的字符之一。该模式并不匹配单词 scattered 中的字符序列 cat,因为它的前一个字符是s、后一个字符是t,这两个字符都不能与 \b 相匹配。

        \b 到底匹配什么东西呢?正则表达式引擎不懂任何人类语言,所以也不知道什么是单词边界。简单地说,\b 匹配的是字符之间的一个位置:一边是单词(能够被 \w 匹配的字母数字字符和下划线),另一边是其他内容(能够被 \W 匹配的字符)。重要的是要认识到,如果想匹配一个完整的单词,就必须在要匹配的文本的前后都加上 \b。

mysql> set @s:='The captain wore his cap and cape proudly as
    '> he sat listening to the recap of how his
    '> crew saved the men from a capsized vessel.';
Query OK, 0 rows affected (0.00 sec)

mysql> set @r:='\\bcap';
Query OK, 0 rows affected (0.00 sec)

mysql> select regexp_extract(@s, @r, '') s, regexp_extract_index(@s, @r, 0, '') i;
+-----------------+-------------+
| s               | i           |
+-----------------+-------------+
| cap,cap,cap,cap | 5,22,30,113 |
+-----------------+-------------+
1 row in set (0.00 sec)

        模式 \bcap 匹配任何以字符序列 cap 开头的单词。这里总共找到了 4 个匹配,其中有 3 个都不是独立的单词 cap。下面这个例子里的文本还是刚才那段文字,但在这次的正则表达式里只有一个 \b 后缀。

mysql> set @r:='cap\\b';
Query OK, 0 rows affected (0.00 sec)

mysql> select regexp_extract(@s, @r, '') s, regexp_extract_index(@s, @r, 0, '') i;
+---------+-------+
| s       | i     |
+---------+-------+
| cap,cap | 22,72 |
+---------+-------+
1 row in set (0.00 sec)

        模式 cap\b 匹配以字符序列 cap 结束的任意单词。这里总共找到了 2 个匹配,其中有一个不是独立的单词 cap。如果只想匹配单词 cap 本身,那么正确的模式应该是 \bcap\b。

        b 匹配的是一个位置,而不是任何实际的字符。用 \bcat\b 匹配到的字符串的长度是 3 个字符(c、a、t),不是 5 个字符。如果不想匹配单词边界,那么可以使用 \B。下面的例子使用 \B 来查找前后都有多余空格的连字符。

mysql> set @s:='Please enter the nine-digit id as it
    '> appears on your color - coded pass-key.';
Query OK, 0 rows affected (0.00 sec)

mysql> set @r:='\\B-\\B';
Query OK, 0 rows affected (0.00 sec)

mysql> select regexp_extract(@s, @r, '') s, regexp_extract_index(@s, @r, 0, '') i;
+------+------+
| s    | i    |
+------+------+
| -    | 60   |
+------+------+
1 row in set (0.00 sec)

        \B-\B 将匹配一个前后都不是单词边界的连字符。nine-digit 和 pass-key 中的连字符不能与之匹配,但 color - coded 中的连字符可以与之匹配,因为空格和连字符都不属于\w。同一个元字符的大写形式与它的小写形式在功能上往往刚好相反。

三、字符串边界

        单词边界可以用来对单词位置进行匹配,如单词的开头、单词的结尾、整个单词等。字符串边界有着类似的用途,只不过用于在字符串首尾进行模式匹配。字符串边界元字符有两个:^ 代表字符串开头,$ 代表字符串结尾。

        有些元字符拥有多种用途,^ 就是其中之一。只有当它出现在字符集合里,即位于 [ 和 ] 之间,且紧跟在左方括号的后面时,它才表示排除该字符集合。如果出现在字符集合之外并位于模式的开头,^ 将匹配字符串的起始位置。为了演示字符串边界的用法,下面准备了一个例子。有效的 XML 文档都必须以 <?xml> 标签开头,另外可能还包含一些其他属性,比如版本号,如<?xml version="1.0" ?>。下面这个简单的测试可以检查一段文本是否为 XML 文档。

mysql> set @s:='<?xml version="1.0" encoding="UTF-8" ?>
    '> <wsdl:definitions targetNamespace="http://tips.cf"
    '> xmlns:impl="http://tips.cf" xmlns:intf="http://tips.cf"
    '> xmlns:apachesoap="http://xml.apache.org/xml-soap"';
Query OK, 0 rows affected (0.00 sec)

mysql> set @r:='<\\?xml.*\\?>';
Query OK, 0 rows affected (0.00 sec)

mysql> select regexp_extract(@s, @r, '') s, regexp_extract_index(@s, @r, 0, '') i;
+-----------------------------------------+------+
| s                                       | i    |
+-----------------------------------------+------+
| <?xml version="1.0" encoding="UTF-8" ?> | 1    |
+-----------------------------------------+------+
1 row in set (0.00 sec)

        该模式似乎管用。<\?xml 匹配 <?xml,.* 匹配随后的任意文本(.的零次或多次重复出现),\?> 匹配结尾的 ?>。但是,这个测试非常不准确。在下面的例子里,采用同样的模式来匹配在 <?xml> 标签之前包含额外内容的文本。

mysql> set @s:='This is bad, real bad!
    '> <?xml version="1.0" encoding="UTF-8" ?>
    '> <wsdl:definitions targetNamespace="http://tips.cf"
    '> xmlns:impl="http://tips.cf" xmlns:intf="http://tips.cf"
    '> xmlns:apachesoap="http://xml.apache.org/xml-soap"';
Query OK, 0 rows affected (0.00 sec)

mysql> select regexp_extract(@s, @r, '') s, regexp_extract_index(@s, @r, 0, '') i;
+-----------------------------------------+------+
| s                                       | i    |
+-----------------------------------------+------+
| <?xml version="1.0" encoding="UTF-8" ?> | 24   |
+-----------------------------------------+------+
1 row in set (0.00 sec)

        模式 <\?xml.*\?> 匹配到的是第 2 行文本。因为 XML 文档的起始标签出现在了第 2 行,所以这肯定不是有效的 XML 文档,将其作为 XML 文档来处理会导致各种问题。这里需要的测试是能够确保 XML 文档的起始标签 <?xml> 出现在字符串最开始处,而这正是 ^ 元字符大显身手的地方。

mysql> set @s:='<?xml version="1.0" encoding="UTF-8" ?>
    '> <wsdl:definitions targetNamespace="http://tips.cf"
    '> xmlns:impl="http://tips.cf" xmlns:intf="http://tips.cf"
    '> xmlns:apachesoap="http://xml.apache.org/xml-soap"';
Query OK, 0 rows affected (0.00 sec)

mysql> set @r:='^\\s*<\\?xml.*\\?>';
Query OK, 0 rows affected (0.00 sec)

mysql> select regexp_extract(@s, @r, '') s, regexp_extract_index(@s, @r, 0, '') i;
+-----------------------------------------+------+
| s                                       | i    |
+-----------------------------------------+------+
| <?xml version="1.0" encoding="UTF-8" ?> | 1    |
+-----------------------------------------+------+
1 row in set (0.00 sec)

        ^ 匹配一个字符串的开头位置,所以 ^\s* 匹配字符串的开头和随后的零个或多个空白字符,这解决了<?xml>标签前允许出现的空格、制表符、换行符的问题。作为一个整体,模式 ^\s*<\?xml.*\?> 不仅能匹配带有任意属性的 XML 起始标签,还可以正确处理空白字符。

        虽然模式 ^\s*<\?xml.*\?> 解决了上例中的问题,但那只是因为这个例子里的 XML 文档并不完整而已。如果采用完整的 XML 文档,就会看到贪婪型量词的典型表现。所以,这个例子很好地说明了什么时候该使用 .*? 代替 .*。$ 的用法也差不多,它可以用来检查 Web 页面结尾的 </html>

猜你喜欢

转载自blog.csdn.net/wzy0623/article/details/130962567