`
javasogo
  • 浏览: 1775556 次
  • 性别: Icon_minigender_1
  • 来自: 北京
文章分类
社区版块
存档分类
最新评论

简单入门正则表达式 - 第八章 捕获群和逆向引用

 
阅读更多
<style> #content-region { background-image: url(http://p.blog.csdn.net/images/p_blog_csdn_net/rcom10002/EntryImages/20081027/watermark.gif); } #content-region h3 { border: 1px dotted #333333; background-color: #f9f9f9; padding: 10px; font-size: 24px; } #content-region p { font-family: "宋体", "仿宋"; font-size: 16px; line-height: 28px; text-decoration: none; text-indent: 32px; } #content-region .regex-pattern { font-style: oblique; font-family: "Courier New", Courier, monospace; background-color: #FFCCCC; border-top-style: solid; border-right-style: none; border-bottom-style: solid; border-left-style: none; border-top-color: #FF0000; border-bottom-color: #FF0000; border-top-width: 1px; border-right-width: 1px; border-bottom-width: 1px; border-left-width: 1px; border-right-color: #FF0000; border-left-color: #FF0000; padding: 0px 5px 0px 5px; } #content-region .regex-result { font-family: "Courier New", Courier, monospace; background-color: #A4FFE1; border-top-style: solid; border-right-style: none; border-bottom-style: solid; border-left-style: none; border-top-color: #339966; border-bottom-color: #339966; border-top-width: 1px; border-right-width: 1px; border-bottom-width: 1px; border-left-width: 1px; border-right-color: #339966; border-left-color: #339966; padding: 0px 5px 0px 5px; } #content-region blockquote { padding: 10px; border-top-width: 1px; border-right-width: 1px; border-bottom-width: 1px; border-left-width: 1px; border-top-style: dotted; border-right-style: dotted; border-bottom-style: dotted; border-left-style: dotted; width: auto; } #content-region img { border: 1px dotted #000000; padding: 16px; background-color: #f9f9f9; margin-top: 16px; margin-right: 16px; margin-bottom: 16px; margin-left: 64px; } #content-region code { white-space: pre; } </style>
<!-- InstanceBeginEditable name="contentRegion" -->

一、捕获组

God said, "Let there be light", and there was light; and God saw that the light was good, and he separated light from darkness. He called the light day, and the darkness night. So evening came, and morning came, the first day.

我们在阅读文章时,常常会根据其中一些词语出现的频度来推断出其中心含义,当然了,更加深层的含义还需要慢慢体会,不过我们可以利用这个简单的方法快速地抓取某些关键词。上面的一段文字摘自圣经创世纪,我们如何找出一个出现次数最多的词呢?这个问题就可以利用本章标题提到的捕获组和逆向引用来解决!

我们先给出答案(/b/w{3,}/b)(.*?/b/1/b){4,},在本章的最后将对这个答案进行详细解释。

在正则表达式中,可以使用圆括号来把字符组织到一起,并把匹配的结果记录下来,我们把组织到一起的字符称为一个捕获群。捕获群的行为与单个字符类似,允许与操作符一起配合使用,如“*”、“|”、“?”等。其中,圆括号也属于元字符的范畴之内,如要与圆括号进行匹配,需要事先进行转义操作。

现在让我们举个例子来看看到底应该如何使用捕获组。下面的表格是一些条码基本术语,每条术语都是由名称和解释两部分组成,现在的任务就是让术语名称和术语解释两个部分进行调换。这时,捕获组就派上用场了,因为它不光能进行匹配操作,还能把匹配的结果记录下来并进行编号,以便我们以后利用。

  1. <html>
  2. <head>
  3. <title>Terms</title>
  4. </head>
  5. <body>
  6. <table>
  7. <tr>
  8. <td>条码 bar code </td>
  9. <td>由一组规则排列的条、空及其对应字符组成的标记,用以表示一定的信息。</td>
  10. </tr>
  11. <tr>
  12. <td>条码系统 bar code system </td>
  13. <td>由条码符号设计、制作及扫描阅读组成的自动识别系统。</td>
  14. </tr>
  15. <tr>
  16. <td>条 bar </td>
  17. <td>条码中反射率较低的部分。</td>
  18. </tr>
  19. <tr>
  20. <td>空 space </td>
  21. <td>条码中反射率较高的部分。</td>
  22. </tr>
  23. </table>
  24. </body>
  25. </html>

首先,我们先建立匹配用的正则表达式(<td>.+?</td>)(<td>.+?</td>)。从表面上看,捕获组的两个部分完全一样,那我们为什么要使用两(<td>.+?</td>),而不是一个(<td>.+?</td>)呢?这是因为我们要分别匹配术语名称和术语解释,然后把匹配后记录下来的结果进行对调,这样就能把术语名称和术语解释两部分的位置交换。下面是匹配后的结果:

<table>
<tr>
<td>条码 bar code </td><td>由一组规则排列的条、空及其对应字符组成的标记,用以表示一定的信息。</td></tr>
<tr>
<td>条码系统 bar code system </td><td>由条码符号设计、制作及扫描阅读组成的自动识别系统。</td></tr>
<tr>
<td>条 bar </td><td>条码中反射率较低的部分。</td></tr>
<tr>
<td>空 space </td><td>条码中反射率较高的部分。</td></tr>
</table>

二、捕获群的编号

接下来,我们学习一下与捕获群密切相关的捕获群编号的概念。当使用含有捕获群的正则表达式进行匹配操作时,引擎会为每一对小括号组合进行编号,如正则表达式(正[规则])(表[达现](式)),引擎就对它进行了四次编号。首先是(正[规则]),然后是(表[达现](式)),最后是(式)。咦?!不是说四次编号吗,怎么少了一次。其实还有一个默认的最大的捕获群,它就是完整的正则表达式本身(正[规则])(表[达现](式))

每个捕获群的编号代表了它所指向的匹配内容,通过它,就能够重复利用匹配的结果。捕获群的编号格式是一个元字符“/”或“$”(一般以“$”更为常见)加上一个数字,数字的范围在 0 至 9 之间。/0$0代表整个正则表达式相匹配的结果,其余的与它们各自指向的捕获群所匹配的结果相对应的。

三、配合使用捕获群与其编号

刚才我们已经把成功地找到了术语名称和术语解释两个部分,现在来看看如何实现位置调换。在每一次匹配操作中,两个(<td>.+?</td>)分别被编上两个序号,我们使用$1$2就能引用它们,所以,调换位置实际上就是对这两个引用进行调换。下面我们用Javascript代码演示一下。


首先先将要调换的表格源代码放到一个多行文本区域里,然后通过点击“交换”按钮来反复切换术语标题和术语说明两个部分。下面是全部的源代码,我们先分析一下正则表达式/(<td>.+?<//td>)(<td>.+?<//td>)/m。Javascript中,正则表达式的内容是用两个“/”夹起来的,当正则表达式的内容含有“/”时,则要用“/”对它进行转义。后一个“/”紧跟着的是正则表达式选项,总共有三个,i、m 和 g,含义分别是忽略大小写、多行匹配模式和全局匹配(找到所有匹配项,而不是首次找到匹配项即停止)。下面的Javascript代码中捕获群的作用我们在前面已经讲过,这里就不再重复,但要对它的“/”进行转义,因为“/”在Javascript属于元字符范畴。因为我们的正则表达式要应用到html代码中的每一行,所以最后要指定“m”多行(Multiline)匹配模式;“g”(Global)的作用是让匹配操作找到所有的匹配内容,而不是在找到第一个匹配后即停止。代码中,真正实现位置调换的代码只有var newText = orgText.replace(/(<td>.+?<//td>)(<td>.+?<//td>)/gm, "$2$1")一行代码,所以说,要是能合理地使用正则表达式,它就能帮助我们轻松地解决很多问题。

  1. <html>
  2. <head>
  3. <title></title>
  4. <script language="JavaScript" type="text/javascript">
  5. function swap() {
  6. var orgText = document.getElementById("testRegion").value;
  7. // alert("原始字符:/n" + orgText);
  8. var newText = orgText.replace(/(<td>.+?<//td>)(<td>.+?<//td>)/gm, "$2$1");
  9. // alert("调换字符:/n" + newText);
  10. document.getElementById("content").innerHTML = newText;
  11. document.getElementById("testRegion").value = newText;
  12. }
  13. </script>
  14. </head>
  15. <body>
  16. <form method="get">
  17. <textarea id="testRegion" name="testRegion" cols="60" rows="16">
  18. &lt;table border=&quot;1&quot;&gt;
  19. &lt;tr&gt;
  20. &lt;td&gt;条码 bar code &lt;/td&gt;&lt;td&gt;由一组规则排列的条、空及其对应字符组成的标记,用以表示一定的信息。&lt;/td&gt;
  21. &lt;/tr&gt;
  22. &lt;tr&gt;
  23. &lt;td&gt;条码系统 bar code system &lt;/td&gt;&lt;td&gt;由条码符号设计、制作及扫描阅读组成的自动识别系统。&lt;/td&gt;
  24. &lt;/tr&gt;
  25. &lt;tr&gt;
  26. &lt;td&gt;条 bar &lt;/td&gt;&lt;td&gt;条码中反射率较低的部分。&lt;/td&gt;
  27. &lt;/tr&gt;
  28. &lt;tr&gt;
  29. &lt;td&gt;空 space &lt;/td&gt;&lt;td&gt;条码中反射率较高的部分。&lt;/td&gt;
  30. &lt;/tr&gt;
  31. &lt;/table&gt;
  32. </textarea>
  33. <input type="button" value="交换" onclick="swap();">
  34. </form>
  35. <div id="content"></div>
  36. </body>
  37. </html>

四、逆向引用

在捕获组中,另外一个常用的功能就是逆向引用。它可以引用指定的捕获组最后一次成功匹配时的匹配内容。我们用 HTML 标记举个例子,如果我们要想匹配字符串“<title>正则表达式</title>”,常用的方法就是<title>.+?</title>。但是这种的写法有个缺点,就是不够通用,如果 HTML 被重构成字符串“<h1>正则表达式</h1>”,就得修改正则表达式为<h1>.+?</h1>,这样的做法明显比较笨拙,有没有更智能点儿的解决方案呢。还好与捕获组相关的一个功能就是逆向引用,它的作用就是把捕获组匹配到的内容记忆下来,然后根据捕获组所对应的索引号,重新利用最近一次的匹配结果,所以,我们的可以把正则表达式修改为<([^>]+)>.+?<//1>

修改后的正则表达式中用小括号夹起来的就是捕获组,它对应的编号为1,如果后续还有更多的捕获组,它们对应的编号就应该是2、3、4 …… 如果要引用一个捕获组的内容,可以使用语法“/捕获组编号”。表达式前半部分<([^>]+)>可以与起始标签“<title>”相匹配,并把“title”记忆下来,当匹配到终止标签的字符“/”后,紧接着就是逆向引用“/1”,它所代表的内容就是“title”。

这里强调一下,捕获组的内容总是最后一次成功匹配时的结果。比如我们用正则表达式(/d+/D+)来匹配字符串“001张三002李四003王五004赵六”,然后用“$1”来进行替换操作,结果只是最后一次匹配的内容004赵六

五、为捕获组命名

前面已经讲过,捕获组可以用编号进行引用,除此之外,它还可以通过名称进行引用。比如与字符串“中国 - CN”相匹配的正则表达式(/w+)/W*(/w+),我们想把原来的字符串改写成“CN - 中国”,就把替换的部分写成$2 - $1

但“$1”和“$2”仅仅是编号,它不能给代码阅读者任何提示性的文字信息,如果能采用“英文缩写 - 名称”这样的形式就好多了,正则表达式支持为捕获组进行命名,在 Python、PCRE(与 Perl 兼容的正则表达式) 和 PHP 中,语法通常为(?P<GroupName>Pattern),但在有的正则引擎中可能会使用其它语法形式,如 .Net 中使用(?<GroupName>Pattern),具体使用时先查看一些相关文档。现在,我们假定在 .Net 环境下把“CN - 中国”做一个国家代码和国家名称的交替操作,把原来的(/w+)/W(/w+)改成(?<country_code>/w+)/W+(?<country_name>/w+),然后替换使用${country_name} - ${country_code},最后得到的结果就是中国 - CN。注意,在为捕获组命名的时候尽量采用大多数编程语言中所定义的变量命名规则,否则可能会出现无法正常解析正则表达式的错误。

六、取消捕获组

利用正则表达式的捕获组能极大地提高灵活性,使字符串操作更加方便。但事物总是具有两面性,捕获组的缺点就是消耗更多的资源来换取它的灵活性。当我们并不想使用逆向引用时,是不需要捕获组记忆任何东西的,这种情况下就可以利用(?:nocapture)语法来主动地告诉正则表达式引擎,不要把圆括号的内容当作捕获组,以便提高效率。

这样,正则表达式abc(?:/w+)就不会对圆括号里面的内容进行逆向引用了,而且它的执行速度也要快于创建捕获组时的速度。

七、捕获组应用

再次回到本章最初的例子中,我们先分析下正则表达式(/b/w{3,}/b)(.*?/b/1/b){4,}。首先是第一个捕获组(/b/w{3,}/b),它代表的是一个单词,两边是词界,中间是一个至少包含三个字母的单词;后面的是(.*?/b/1/b){4,},其中“/1”是对第一个捕获组的引用,而且重复的次数至少是四次。

想要找出段落中出现次数最多的词,首先将语句中的冠词、助词、代词等内容用表达式/b(the|they|them)/b一次性去除,对于不同的文章,我们可以选定一些特定词的词作为要去除的对象。接下来,我们将表达式中控制匹配次数的{4,}设置为一个较大的数字,比如{10,},然后逐次减一来找到出现频度最高的词,当使用“4”来尝试时,我们就可以找到单词“light”了。此时,如果我们把“light”全部替换掉,就可以利用表达式继续查找下一个高频度词了。

<!-- InstanceEndEditable -->
分享到:
评论

相关推荐

Global site tag (gtag.js) - Google Analytics