前一章中介绍了如何使用子表达式将字符分成组。这种分组的主要用途之一是可以控制组的重复次数(在前一章中已经演示过)。本章中将介绍子表达式一个更重要的用法——使用后向引用。
理解后向引用
理解后向引用需求的最好办法是看一个例子。
HTML
开发者经常使用段落标签(
<H1>
到
<H6>
,包括相应的结束标签)来定义
Web
页面的提纲。假设你需要定位所有的段落标签,而不管相应的段落级别。下面是一个例子:
文本
<BODY>
<H1>Welcome to my Homepage</H1>
Content is divided into two
sections:<BR>
<H2>ColdFusion</H2>
Information about Macromedia
ColdFusion.
<H2>Wireless</H2>
Information about Bluetooth, 802.11, and more.
</BODY>
正则表达式
<[hH]1>.*</[hH]1>
结果
<BODY>
<H1>Welcome to my Homepage</H1>
Content is divided into two
sections:<BR>
<H2>ColdFusion</H2>
Information about Macromedia
ColdFusion.
<H2>Wireless</H2>
Information about Bluetooth, 802.11, and more.
</BODY>
分析
这里的模式“<[hH]1>.*</[hH]1>”匹配了第一个段落(从
<H1>
到
</H1>
),同样可以匹配
<h1>
(
HTML
不是大小写敏感的)。但是什么模式可以匹配所有的六种段落(六种中的任何一种都是合法的)?
一种方案是将前面的
1
换成数字区间,如下面所示:
文本
<BODY>
<H1>Welcome to my Homepage</H1>
Content is divided into two
sections:<BR>
<H2>ColdFusion</H2>
Information about Macromedia
ColdFusion.
<H2>Wireless</H2>
Information about Bluetooth, 802.11, and
more.
</BODY>
正则表达式
<[hH][1-6]>.*?</[hH][1-6]>
结果
<BODY>
<H1>Welcome to my Homepage</H1>
Content is divided into two
sections:<BR>
<H2>ColdFusion</H2>
Information about Macromedia
ColdFusion.
<H2>Wireless</H2>
Information about Bluetooth, 802.11, and more.
</BODY>
分析
这看起来可以工作:“<[hH][1-6]>”可以匹配任何开始的段落标签(在例子中包括<H1>和<H2>),而“<[hH][1-6]>”可以匹配所有的结束标签(在例子中为<
/
H1>和<
/
H2>)。
注意:我们这里使用了“.*?”而不是“.* ”。正如在第五章解释的一样,如“
*
”的量词是贪婪的,所以模式“<[hH][1-6]>.*</[hH][1-6]>”将匹配从第二行中的
<H1>
直到第六行中的
</H2>
。可以使用非贪婪量词“
.*?
”来解决这个问题。
我说的是可能,而不是可以,因为这个特定的例子中即使是使用贪婪量词也是可以解决问题的。因为这里的元字符“
.
”不能匹配换行符,而在这个例子中,段落标签都是位于单独的行中。但是这里使用非贪婪的量词匹配符是没有副作用的,最好使用安全的模式。
成功了吗?还没有。看看下面的例子(使用了同样的模式):
文本
<BODY>
<H1>Welcome to my Homepage</H1>
Content is divided into two
sections:<BR>
<H2>ColdFusion</H2>
Information about Macromedia
ColdFusion.
<H2>Wireless</H2>
Information about Bluetooth, 802.11, and more.
<H2>This is not valid HTML</H3>
</BODY>
正则表达式
<[hH][1-6]>.*?</[hH][1-6]>
结果
<BODY>
<H1>Welcome to my Homepage</H1>
Content is divided into two
sections:<BR>
<H2>ColdFusion</H2>
Information about Macromedia
ColdFusion.
<H2>Wireless</H2>
Information about Bluetooth, 802.11, and more.
<H2>This is not valid HTML</H3>
</BODY>
分析
采用
<H2>
开始而采用
</H3>
的段落标题标签是非法的,但是现在的模式可以匹配。
这里的问题在于匹配的第二个部分(匹配结束的标签)没有办法知道匹配第一部分的知识(匹配开始的标签)。这也是为什么后向引用重要的原因。
使用后向引用匹配
在后面将会重新来看上面的例子。现在我们来看一个简单的例子,一个如果不使用后向引用就不能解决问题的例子。
假设现在有一段文本,你希望找到所有重复的单词(笔误使得单词出现了两次)。很显然,在搜索单词的第二次出现的时候,必须首先知道此单词。后向引用允许正则表达式模式参照前面的匹配内容(在这个例子中,就是第一次匹配的单词)。
理解这个特性的最好方式就是看看它的使用。下面的文本中包含了三组需要定位的重复单词:
文本
This is a block of of text,
several words here are are
repeated, and and they
should not be.
正则表达式
[ ]+(\w+)[ ]+\1
结果
This is a block of of
text,
several words here are are
repeated, and and
they
should not be.
分析
此模式可以工作,但是为什么可以工作呢?“[ ]+ ”匹配一个或者更多空格,“\w+”匹配一个或者更多的文字数字式字符,而“[
]+”则用来匹配尾部的空格。但是注意到这里的“
\w+
”加上了括号使其成为子表达式。此子表达式并不是用于重复匹配,而且本例中也不需要重复。这里的子表达式仅仅是对表达式进行分组,标记此子表达式供以后使用。模式的最后部分是“
\1
”,这是对子表达式的后向引用,所以当“
\w+
”匹配了单词
of
,“
\1
”也将匹配
of
,当“
\w+
”匹配了单词
and
,“
\1
”也将匹配
and
。
注意:术语后向应用是因为这些实体将引用以前的子表达式。
但是“
\1
”的实际含义是什么呢?它匹配模式中第一个子表达式。同理,“
\2
”将匹配第二个子表达式,“
\3
”将匹配第四个,依此类推。“[ ]+(\w+)[
]+\1”因此将可以匹配所有重复出现的单词。
提示:你可以将后向应用理解成变量。
现在你已经看到了后向引用的用法,再来看看前面的
HTML
例子。使用后向引用,可以创建一个模式用来匹配开始标签和结束标签(忽略所有不匹配的标签对)。下面是这个例子:
文本
<BODY>
<H1>Welcome to my Homepage</H1>
Content is divided into two
sections:<BR>
<H2>ColdFusion</H2>
Information about Macromedia
ColdFusion.
<H2>Wireless</H2>
Information about Bluetooth, 802.11, and more.
<H2>This is not valid HTML</H3>
</BODY>
正则表达式
<[hH]([1-6])>.*?</[hH]\1>
结果
<BODY>
<H1>Welcome to my Homepage</H1>
Content is divided into two
sections:<BR>
<H2>ColdFusion</H2>
Information about Macromedia
ColdFusion.
<H2>Wireless</H2>
Information about Bluetooth, 802.11, and more.
<H2>This is not valid HTML</H3>
</BODY>
分析
同样的,在这里找到了三个匹配:一个<H1> 对和两个
<H2>对。就像以前一样,“<[hH]([1-6])>”将匹配任何的段落标签。但是和以前不一样的是,这里的“
[1-6]
”使用了小括号括起来成为了子表达式。这样,匹配结束标签的模式可以通过“</[hH]\1>”中的“
\1
”来引用此子表达式。“
(1-6)
”是一个可以匹配数字
1
到
6
的子表达式,“
\1
”因此可以匹配相同的数字。在这种情况下,“<H2>This is
not valid HTML</H3>”将不能匹配。
笔记:非常遗憾的是,后向引用语法在不同的正则表达式实现中是不一样的。
JavaScript
中使用
\
来表示后向引用(除了
$
使用时的替换操作),Macromedia
ColdFusion 和vi也是这样。
Perl
语言使用的是
$
(所以
$1
表示这里的
\1
)。
.NET
正则表达式支持返回一个包含匹配名为
Groups
属性的对象,所以
C#
中的match.Groups[1]将引用第一个匹配,Visual Basic
.NET中的match.Groups(1)将引用第一个匹配。
PHP
通过名为
$matches
的数组返回此信息,所以
$matches[1]
引用第一个匹配(尽管可以通过标志来改变)。在
JAVA
和
Python
语言中则返回包含一个数组名为
group
的匹配对象。
具体的正则表达式实现相关信息可以参看附录
1
:流行应用和语言中的正则表达式。
注意:后向引用只能够引用子表达式(需要使用小括号括起来)。
提示:引用的匹配一般是从
1
开始。在大多数的实现中,匹配
0
可以用来引用整个表达式。
笔记:正如你所看到的,子表达式是通过相对位置来引用的:
\1
引用第一个,
\5
引用第五个,等等。尽管获得了广泛的支持,这个语法有着一个严重的问题:移动或者修改子表达式(也因此改变了子表达式的顺序)将会破坏模式,增加或者删除子表达式将会带来更大的问题。为了能够克服这个缺点,现在有些新的正则表达式实现支持命名引用,也就是说为每个可能引用的子表达式给定一个唯一的名称,在以后可以通过此名称来引用(而不是相对位置)。命名引用在本书中并没有包含,因为这还不是一个广泛支持的特性,而且支持此特性的正则表达式实现的语法都很不一样。尽管如此,如果你使用的应用或者语言支持命名引用的话(如
.NET
),最好是利用这种特性的好处。
执行替换操作
到现在为止我们所看到的正则表达式都是进行搜索——在一段文本中定位单词。事实上,可能确实大部分的正则表达式都是用来进行搜索。但是这不是正则表达式可以做的所有事情——正则表达式还可以用来执行替换操作。举个例子,将
CA
替换成California和将MI替换成Michigan
并不是正则表达式需要完成的工作。尽管使用正则表达式也是合法的,但是没有必要这么做。事实上,在这里如果使用简单的字符串操作函数的话过程将会变得更加容易。
正则表达式只有在使用后向引用的时候才变得有竞争性。下面是在第五章中使用过的例子:
文本
Hello, ben@forta.com is my email address.
正则表达式
\w+[\w\.]*@[\w\.]+\.\w+
结果
Hello, ben@forta.com
is my email address.
分析
这个模式匹配了一段文本中的邮件地址(已经在第五章中进行了解释)。
但是如果你现在希望将所有的邮件地址修改为可点击的该怎么做?在
HTML
语言中可以使用“<A
HREF="
mailto:user@address.com">user@address.com</A
>”
来创建一个可点击的邮件地址。是否可以通过正则表达式改变为可点击的邮件地址?事实上,是而且非常简单(当你知道如何使用后向引用的时候)。
文本
Hello, ben@forta.com is my email
address.
正则表达式
(\w+[\w\.]*@[\w\.]+\.\w+)
替换
<A HREF="mailto:$1">$1</A
>
结果
Hello, <A
HREF="mailto:ben@forta.com">ben@forta.com</A
>
is my email address.
分析
在替换操作中,将使用两个正则表达式:其中一个指定搜索模式,第二个指定需要匹配的文本。后向引用可能会跨越模式,所以在第一个模式中匹配的子表达式将会用于第二个模式中。“(\w+[\w\.]*@[\w\.]+\.\w+)”和前面的模式是一样的(定位邮件地址),但是这里指定了一个子表达式。这样匹配的文本将可以用于替换模式。“<A
HREF="
mailto:$1">$1</A
> ”
使用了匹配的子表达式两次——第一个用为
HREF
的属性(定义
mailto:
),后面则定义了可点击文本。所以,“
ben@forta.com
”将变为“<A HREF="
mailto:ben@forta.com">ben@forta.com</A
>”,这也是我们所需要的。
注意:前面已经提到过,你可能需要根据实现来修改后向引用。在这里,JavaScript用户将使用
$
来替代前面的
\
。而对于ColdFusion 用户则可以使用
\
来执行搜索和替换操作。
提示:就像在这个例子中看到的,一个子表达式可以通过后向引用根据需要简单的引用多次。
让我们再来看一个例子。用户信息保存在数据库中,其中电话号码使用“313-555-1234”的格式保存。现在,需要重新格式化电话号码为“(313)
555-1234”。下面是这个例子:
文本
313-555-1234
248-555-9999
810-555-9000
正则表达式
(\d{3})(-)(\d{3})(-)(\d{4})
替换
($1) $3-$5
结果
(313) 555-1234
(248) 555-9999
(810) 555-9000
分析
同样的,这里使用了两个正则表达式模式。第一个看起来很复杂,实际上是比较简单的。“(\d{3})(-)(\d{3})(-)(\d{4})”匹配了一个电话号码,并分成了五个子表达式(五个部分)。“(\d{3})”匹配刚开始的三个数字并作为第一个子表达式,“
(-)
”匹配“
-
”并作为第二个子表达式,依此类推。最后的结果是将此电话号码分为了五个部分(每个部分都是一个子表达式):区域码,连字符,号码前三个数字,连字符,号码后四个数字。这四个部分可以根据需要单独引用,所以“($1)
$3-$5”只是使用了其中的三个子表达式,而忽略了另外的两个,因此“313-555-1234”改变为了“(313) 555-1234”。
提示:在对文本重新格式化的时候,一般来说会将文本变为许多小的子表达式,这可以更好地控制文本。
改变大小写
有些正则表达式实现还支持通过表
8.1
中的元字符来改变大小写。
元字符
|
描述
|
\E
|
终止\L 或者 \U 的转换
|
\l
|
将接下来的字符改为小写
|
\L
|
将接下来的所有字符改为小写直到遇到
\E
|
\u
|
将接下来的字符改为大写
|
\U
|
将接下来的所有字符改为大写直到遇到
\E
|
表8.1.
改变大小写的元字符
\l
和
\u
放置在字符或者子表达式之前用来转换接下来的字符大小写。
\L
和
\U
用来转会接下来的所有字符大小写直到遇到
\E
。下面是一个简单的例子,将
<H1>
标签对中的文字改为大写:
文本
<BODY>
<H1>Welcome to my Homepage</H1>
Content is divided into two
sections:<BR>
<H2>ColdFusion</H2>
Information about Macromedia
ColdFusion.
<H2>Wireless</H2>
Information about Bluetooth, 802.11, and more.
<H2>This is not valid
HTML</H3>
</BODY>
正则表达式
(<[Hh]1>)(.*?)(</[Hh]1>)
替换
$1\U$2\E$3
结果
<BODY>
<H1>WELCOME TO MY HOMEPAGE</H1>
Content is divided into two
sections:<BR>
<H2>ColdFusion</H2>
Information about Macromedia
ColdFusion.
<H2>Wireless</H2>
Information about Bluetooth, 802.11, and more.
<H2>This is not valid HTML</H3>
</BODY>
分析
此模式“(<[Hh]1>)(.*?)(</[Hh]1>)”将段落标签内容分解成三个部分:开始标签、文本和结束标签。第二个模式中将这些内容放在了一起:
$1
包含了开始标签,“ \U$2\E”转换了第二个子表达式到其大写形式,
$3
中包含了结束标签。
小结
子表达式用来定义了一组字符。除了可以用来进行重复匹配以外(在前一章中已经演示过),子表达式还可以用来引用。这种引用被称为后向引用(非常遗憾的是,后向引用的语法并不相同)后向引用在文本匹配和替换操作中都很有用。
亦歌亦行
@
http://searun.iteye.com
分享到:
相关推荐
5. 回溯引用:回溯引用允许我们在正则表达式中使用之前已经匹配的子表达式,通常通过反斜杠后跟数字来实现。 6. 非捕获组:非捕获组用来指定一个组,但是不需要捕获它用于后续的引用,通常通过(?:pattern)来表示...
6. **分组与后向引用**:使用圆括号`()`可以创建分组,分组内的模式可以作为一个整体处理。后向引用如`\1`、`\2`等可以引用前面分组匹配的内容。 7. **量词**:`*`、`+`、`?`和`\{n,m\}`等量词可以控制匹配次数,`*...
以下是我整理的一些正则表达式的常用例子,旨在帮助理解其基本用法和功能。 1. **基本匹配** - 匹配单个字符:`\d` 代表数字,`\w` 代表字母或数字,`\s` 代表空白符。 - 匹配范围:`[a-z]` 匹配小写字母,`[A-Z]...
例如,当用户在输入框(`<input>`)中输入数据后,可以触发一个JavaScript函数,该函数使用正则表达式对输入内容进行验证。 正则表达式验证的基本流程可能如下: 1. 用户在HTML表单中输入数据。 2. 当用户提交表单...
3.10 如果我不使用表达式的值, 我应该用++i 或i++ 来自增一个变量 吗? . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 15 3.11 为什么如下的代码int a = 100, b = 100; long int c = a * b;...
### 跟我学写Makefile #### 概述与目的 本文档旨在通过系统而详尽的方式指导读者学习如何编写Makefile。Makefile是一种用于自动化构建过程的脚本文件,广泛应用于软件开发中,特别是在大型项目的编译过程中。通过...
该命令表示把源串内的match都替换成replace,s指示match可以是正则表达式。 g表示把每行内所有match都替换,如果去掉g,则只有每行的第1处match被替换(实际上不需要g,因为一个.d文件中,只会在开头有一个main.o:)。...
Blade模板通过简洁的指令大大简化了模板代码,并且编译速度快,因为它只用到了少量的正则表达式来编译模板。 数据库是Web应用程序中不可或缺的一部分,Laravel通过迁移(Migration)系统简化了数据库的管理。迁移...
o 7.7 有人跟我讲, 数组不过是常指针。 o 7.8 我遇到一些 ``搞笑" 的代码, 包含 5["abcdef"] 这样的 ``表达式"。 这为什么是合法的 C 表达式呢 ? o 7.9 既然数组引用会蜕化为指针, 如果 arr 是数组, 那么 arr 和...
6.9 有人跟我讲,数组不过是常指针。这样讲准确吗? 6.10 我还是很困惑。到底指针是一种数组,还是数组是一种指针? 6.11 我看到一些“搞笑”的代码,包含5["abcdef"]这样的“表达式”。这为什么是合法的C语言...
3.14 如果我不使用表达式的值,那我应该用i++还是++i来做自增呢? 39 3.15 我要检查一个数是不是在另外两个数之间,为什么if(a b c)不行? 40 3.16 为什么如下的代码不对?int a=1000, b=1000; long int c=a * ...