你现在已经学习到了如何在文本中的任意位置匹配各种字符及其组合以及任意的次数。尽管如此,有时候需要在特定的位置匹配文本。这就是将在本章要介绍的位置匹配。
使用边界
边界处理表示在特定的位置指定匹配文本。为了理解位置匹配的需求,考虑下面的例子:
文本
The cat scattered his food all over the room.
正则表达式
cat
结果
The cat scat tered his food all over the room.
分析
此模式将匹配所有出现过的 cat ,即使此单词是出现在“scattered”中。事实上,这并不是我们所需要的。如果你将所有出现过的 cat 都变为 dog ,则将会出现下面的结果:
The dog sdogtered his food all over the room.
这使得我们需要边界匹配,也就是说使用特殊的元字符在模式中指定边界。
使用单词边界
第一个边界,也是最常用的是通过使用“ \b ”指定字符边界。就像其名字所描述的那样,“ \b ”用来匹配单词的开始和结束。
为了演示“ \b ”的用法,下面是前一个例子并使用了指定的边界:
文本
The cat scattered his food all over the room.
正则表达式
\bcat\b
结果
The cat scattered his food all over the room.
分析
单词 cat 的前面和后面都有一个空格。所以可以匹配“\bcat\b”(空格是一种用来分隔单词的字符)。而在scattered中的 cat 则不会被匹配,因为 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
结果
The cap tain wore his cap and cap e proudly as
he sat listening to the recap of how his
crew saved the men from a cap sized vessel.
分析
模式“ \bcap ”匹配了任何以“ cap ”开始的单词,所以匹配了四个单词,其中三个并不是单词 cap 。下面是一个相同的例子,但是使用了后置“ \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.
正则表达式
cat\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.
分析
“ cap\b ”匹配使用 cap 结束的任意单词,所以找到了两个匹配,包括一个不是 cap 的单词。
如果需要匹配指定的单词,正确的模式是使用“\bcap\b”。
笔记:“ \b ”并不是匹配一个字符;实际上是匹配一个位置。所有模式“ \bcat\b ”实际上将匹配三个字符“ cat ”,而不是五个字符的长度。
为了指定不在单词边界匹配,可以使用“ \B ”。下面的例子使用了“ \B ”元字符来定位两边是空格的元字符。
文本
Please enter the nine-digit id as it
appears on your color - coded pass-key.
正则表达式
\B-\B
结果
Please enter the nine-digit id as it
appears on your color - coded pass-key.
分析
模式“\B-\B”匹配了含有断字符的连字符。所以“nine-digit”和“pass-key”字符中的“ - ”不能匹配,而“color - coded”则可以。就像在第四章中看到的一样,大写的元字符和小写的元字符的功能是相反的。
注意:有些正则表达式实现支持另外两个元字符。“ \b ”匹配单词的开始和结束,“\<”匹配一个单词的开始,“ \> ”则匹配一个单词的结束。尽管这些元字符提供了额外的控制,但是对于这些的支持是有限的(在 egrep 是支持的,但是大部分实现中并不支持)。
定义字符串边界
字符边界用于单词位置的匹配(单次的开始,单词的结束,整个单词等)。字符串边界用来匹配整个字符串的开始和结束。字符串边界匹配的元字符是“ ^ ”和“ $ ”,分别用于字符串的开始和接受。
注意:在第三章中,你已经学习到了“ ^ ”可以用于对字符集的否定。但是它又如何来指示一个字符串的开始呢?
“ ^ ”是几个有几种用途的元字符中的一个。如果位于集合开始处的话,则表示否定;如果在集合外面,则将匹配字符串的开始位置。
为了演示字符串边界的用法,看看下面的例子。合法的 XML 文档开始于<?xml>并且有部分额外属性(可能是版本号,如<xml version="1.0" ?>)。下面是一个检测是否为合法 XML 文档的方法:
文本
<?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"
正则表达式
<\?xml.*\?>
结果
<?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"
分析
此模式看起来可以工作。<\?xml可以匹配<?xml,“ .* ”匹配任何字符(零个或者一个“ . ”实例),\?> 则匹配?>。但实际上这并不是一个准确的测试。看看下面的例子,使用同样的模式匹配文本:
文本
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"
正则表达式
<\?xml.*\?>
结果
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"
分析
模式“<\?xml.*\?> ”匹配了第二行文本。尽管这个 XML 标签可以在第二行中找到,但是这个例子却是非法的(对这个 XML 文档的处理将会出现问题)。
这里的需求是需要保证 XML 标签是出现在字符串的开始位置,而这正是“ ^ ”元字符所能够做的:
文本
<?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"
正则表达式
^\s*<\?xml.*\?>
结果
<?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"
分析
“ ^ ”匹配了字符串的开始位置。“^\s*”因此可以匹配文本的开始位置,后跟着可选的空格字符(在合法的 XML 文档中可以包括空格、制表符和换行等)。完整的“^\s*<\?xml.*\?>”匹配了开始的 XML 标签并正确处理了空格字符。
提示:模式“^\s*<\?xml.*\?>”可以工作,但是只有这里的 XML 例子是不完整的。对于一个完整的 XML 例子,你可能会发现贪婪的匹配可用。也就是说,有些时候可以使用“.*?”而不是“.*”。
“ $ ”用法是一样的。下面的模式可以用来检查在 Web 页面的</html>标签之后没有其他内容。
</[Hh][Tt][Mm][Ll]>\s*$
H, T, M, 和L每个字符都使用了集合,可以用来处理各种不同的大小写组合,同时\s*$可以匹配字符串后的所有空白字符。
注意:模式“^.*$”是一个语法上正确的正则表达式,此模式总是可以找到一个匹配,所以其实也是无用的。你能知道此模式匹配什么,什么时候不能找到一个匹配吗?
使用多行模式
“^”匹配字符串的开始,“ $ ”匹配字符串的结束。但这是一般情况,有一个例外,或者说,有一种方法可以改变这种行为。
许多的正则表达式实现都支持使用特殊的元字符来改变其他元字符的行为,其中一个是“ (?m) ”,可以启用多行模式。在多行模式下,正则表达式引擎将换行符作为字符串的分隔符,所以“^”将匹配文本的开始或者一行的开始,而“ $ ”则可以匹配文本的结束或者是一行的结尾处。
如果使用多行模式的话,(?m)必须放置在模式的开始处,就像下面的例子一样。这个例子的正则表达式用来在代码中定位 JavaScript 注释:
文本
<SCRIPT>
function doSpellCheck(form, field) {
// Make sure not empty
if (field.value == '') {
return false;
}
// Init
var windowName='spellWindow';
var spellCheckURL='spell.cfm?formname=comment&fieldname='+field.name;
...
// Done
return false;
}
</SCRIPT>
正则表达式
(?m)^\s*//.*$
结果
<SCRIPT>
function doSpellCheck(form, field) {
// Make sure not empty
if (field.value == '') {
return false;
}
// Init
var windowName='spellWindow';
var spellCheckURL='spell.cfm?formname=comment&fieldname='+field. name;
// Done
return false;
}
</SCRIPT>
分析
“^\s*//.*$”将匹配字符串的开始,任意空格后接“ // ”(定义了 JavaScript 的注释),然后是任何文本,最后是文本的结束。但是这个模式只对最开始的第一个注释起作用(并且需要是唯一的文本)。“ (?m) ”修饰符“(?m)^\s*//.*$”将换行符也作为字符串的分隔符,所以所有的注释都将被匹配。
注意:“(?m)”在大部分的正则表达式实现中并不支持。
笔记:有些正则表达式实现还支持使用“ \A ”匹配字符串的开始,“ \Z ”匹配字符串的结束。如果支持的话,则这些元字符的功能和“ ^ ”“ $ ”是一样的。但是和“ ^ ”“ $ ”不同的是,这些元字符不能使用(?m)修饰,所以也不能用于多行模式。
小结
正则表达式可以匹配文本和字符串的指定位置。“ \b ”用来指定字符边界(“ \B ”刚好相反)。“ ^ ”和“ $ ”标记了字符串边界,分别匹配字符串的开始和结束。当使用(?m)修饰符的时候,还可以匹配一行的开始和结尾。