三亩地 三亩地SAN MU DI · CODE DIARY
ARTICLE DETAIL

日记详情

真实记录编程学习的某一天,欢迎挑你感兴趣的翻一翻。

正则表达式——位置匹配

正则表达式——位置匹配

位置匹配

    • 1、边界
    • 2、单词边界
    • 3、字符串边界
      • 3.1、分行匹配模式

1、边界

位置匹配用来解决在什么地方进行字符串匹配操作的问题。为了让大家对位置匹配及其相关概念有一个直观的认识,我们先来看一个例子:

文本:

The cat scattered his food all over the room.

正则表达式:

cat

结果:

模式cat把原始文本里的所有cat都找了出来,单词scattered里的那个cat也不例外。但这一结果并不是我们所预期的,我们只想把单词cat本身找出来。我们本想用这种办法把所有的cat替换为dog,但得到的结果却是一个毫无实际意义的句子:

能够正确解决这个问题的办法只有一个:使用边界限定符,也就是在正则表达式里用一些特殊的元字符来表明我们想让匹配操作在什么位置(或边界)发生。

2、单词边界

第一种边界(也是最常用的边界)是由限定符\b指定的单词边界。顾名思义,\b用来匹配一个单词的开始或结尾。

为了演示\b的用法,让我们回到刚才的例子再做一次尝试,但我们这次将用上单词边界:

文本:

The cat scattered his food all over the room.

正则表达式:

\bcat\b

结果:


在原始文本里,单词cat的前后都有一个空格,而这将与模式\bcat\b相匹配(空格是用来分隔单词的字符之一)​。单词scattered中的字符序列cat不能与这个模式相匹配,因为它的前一个字符是s、后一个字符是t(这两个字符都不能与\b相匹配)​。

\b到底匹配什么东西呢?正则表达式引擎不懂英语(事实上,它不懂任何人类语言)​,也不知道什么是单词边界。简单地说,\b匹配的是一个这样的位置,这个位置位于一个能够用来构成单词的字符(字母、数字和下划线,也就是与\w相匹配的字符)和一个不能用来构成单词的字符(也就是与\W相匹配的字符)之间。

这里要特别注意的是,如果你想匹配一个完整的单词,就必须在你想要匹配的文本的前后都加上\b限定符。请看下面这个例子:

文本:

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.

正则表达式:

\bcap

结果:

模式\bcap将匹配以字符序列cap开头的任何一个单词。这里总共找到了4个匹配,其中有3个是以字符序列cap开头的其他单词而不是单词cap本身。

下面这个例子里的原始文本还是刚才那段文字,但在这次的正则表达式里只有一个后缀的\b限定符:

正则表达式:

cap\b

结果:

模式cap\b将匹配以字符序列cap结束的任何一个单词。

这里总共找到了2个匹配,其中一个是以字符序列cap结束的其他单词而不是单词cap本身。

如果你只想匹配单词cap本身,就必须使用\bcap\b做为模式,它才是你需要的正确答案。

如果你想表明不匹配一个单词边界,请使用\B。在下面的例子里,我们将使用\B来查找其前后都有多余空格的连字符:

文本:

Please enter the nine-digit id as it appears on your color - coded pass-key.

正则表达式:

\B-\B

结果:


\B-\B将匹配一个前后都不是单词边界的连字符。nine-digit和pass-key中的连字符不能与之匹配,但color-coded中的连字符可以与之匹配。

注意 除了用来匹配单词边界(开头或结束均可)的\b,有些正则表达式实现还支持另外两个元字符:<只匹配单词的开头;>只匹配单词的结束。不过,虽然这两种元字符可以提供粒度更细的控制,但支持它们的正则表达式引擎却并不多见(据笔者所知,egrep程序是支持<和>的,但许多其他文本匹配工具则不支持它们)​。

3、字符串边界

单词边界可以用来进行与单词有关的位置匹配(单词的开头、单词的结束、整个单词,等等)​。字符串边界有着类似的用途,只不过是用来进行与字符串有关的位置匹配而已(字符串的开头、字符串的结束、整个字符串,等等)​。用来定义字符串边界的元字符有两个:

  • 一个是用来定义字符串开头的^
  • 另一个是用来定义字符串结尾的$

为了演示字符串边界的用法,我们在下面准备了一个例子。合法的XML文档都必须以<? xml>标签开头并有一些其他属性(比如一个版本号,如<? xml version="1.0" ?>)​。下面这个简单的测试可以检查一段文本是否是一篇XML文档:

文本:

<?xml version="1.0" encoding="UTF-8" ?><wsdl:definitionstargetNamespace="http://tips.cf"xmlns:impl="http://tips.cf"xmlns:intf="http:tips.cf"xmlns:apachesoap="http://xml.apache.org/xml-soap"/>

正则表达式:

<\?xml.*\?>

结果:

这个模式似乎能够解决问题:<? xml匹配<? xml, .*匹配随后的任意文本(.的零次或多次重复出现), ? >匹配?>。

这是一个非常不准确的测试。在下面的例子里,上例中的模式虽然匹配到了一个XML文档的开头部分,但位置却完全不对。它匹配到的语句位于文档的第2行而不是第1行。

文本:

This is bad, real bad!<?xml version="1.0" encoding="UTF-8" ?><wsdl:definitionstargetNamespace="http://tips.cf"xmlns:impl="http://tips.cf"xmlns:intf="http:tips.cf"xmlns:apachesoap="http://xml.apache.org/xml-soap"/>

正则表达式:

<\?xml.*\?>

结果:

模式<? xml? >匹配到的是整个文本的第2行。虽然它也是XML文档的开始标签,但因为出现在文本的第2行,所以这份文档肯定不是一份合法的XML文档,把它当做一份XML文档来处理会导致种种问题。

这里需要的是一个能够确保被匹配到的<? xml>标签出现在字符串最开始处的测试,而这正是^元字符大显身手的地方;如下所示:

文本:

<?xml version="1.0" encoding="UTF-8" ?><wsdl:definitionstargetNamespace="http://tips.cf"xmlns:impl="http://tips.cf"xmlns:intf="http:tips.cf"xmlns:apachesoap="http://xml.apache.org/xml-soap"/>

正则表达式:

^\s*<\?xml.*\?>

结果:

^匹配一个字符串的开头位置,所以^\s*将匹配一个字符串的开头位置和随后的零个或多个空白字符(这解决了<?xml>标签前允许有空格、制表符、换行符等空白字符的问题)​。作为一个整体,模式^\s*<\? xml.*\? >不仅能正确地匹配一个位置正确的<? xml>标签,还能对合法的空白字符做出妥善处理。

提示 虽然模式^\s*<? xml.*? >解决了上例中的问题,但那只是因为这个例子里的原始文本并不完整而已。如果这段原始文本是一份完整的XML文档,这个例子将变成一个“贪婪型”元字符的典型示例。还好,我们已经知道解决“贪婪型”元字符问题的最佳办法是把.替换为.?。

除了位置上的差异,$的用法与^完全一样。比如说,在一份Web页面里,</html>标签的后面不应该再有任何实际内容,而这一点可以用下面这个模式来检查:

正则表达式:

</[Hh][Tt][Mm][Ll]\s*$

我们用了4个字符集合来分别匹配H、T、M、L等4个字符(这样就可以对这几个字符的各种大小写组合形式进行处理了), \s*$匹配一个字符串结尾处的零个或多个空白字符。

3.1、分行匹配模式

我们刚刚讲过,^匹配一个字符串的开头,$匹配一个字符串的结尾。但这一结论并非绝对正确,它还有一个例外或者说有一种改变这种行为的办法。

有许多正则表达式都支持使用一些特殊的元字符去改变另外一些元字符行为的做法,用来启用分行匹配模式(multiline mode)的(?m)记号就是一个能够改变其他元字符行为的元字符序列。

分行匹配模式将使得正则表达式引擎把行分隔符当做一个字符串分隔符来对待。在分行匹配模式下,^不仅匹配正常的字符串开头,还将匹配行分隔符(换行符)后面的开始位置(这个位置是不可见的)​;类似地,$不仅匹配正常的字符串结尾,还将匹配行分隔符(换行符)后面的结束位置。

在使用时,(?m)必须出现在整个模式的最前面,就像下面这个例子里那样。在这个例子里,我们将使用一个正则表达式把一段JavaScript代码里的注释内容全部查找出来:

文本:

<SCRIPT>functiondoSpellCheck(form,field){//Make sure not emptyif(field.value==''){returnfalse;}// InitvarwindowName='spellWindow';varspellCheckURL='spell.cfm?formname-comment&fieldname-'+field.name;...// Donereturnfalse;}</SCRIPT>

正则表达式:

(?m)^\s*\/\/.*$

结果:

^\s*\/\/.*$将匹配一个字符串的开始,然后是任意多个空白字符,再后面是\/\/(JavaScript代码里的注释标签)​,再往后是任意文本,最后是一个字符串的结束。不过,这个模式只能找出第一条注释(并认为这条注释将一直延续到文件的末尾,因为*是一个“贪婪型”元字符)​。加上(?m)前缀之后,​(? m)^\s*\/\/.*$将把换行符视为一个字符串分隔符,这样就可以把每一行注释都匹配出来了。

← 返回列表