`
searun
  • 浏览: 176567 次
  • 性别: Icon_minigender_1
  • 来自: 合肥
社区版块
存档分类
最新评论

[跟我学正则表达式] 7. 使用子表达式

阅读更多

在前面的章节中演示过,元字符和字符匹配为正则表达式提供了基本的能力。在本章中将学习到使用子表达式如何进行分组。

 

理解子表达式

匹配多次出现的字符已经在第五章中进行了介绍。在那章中,“\d+”可以匹配一个或者多个数字,“https?://”可以匹配http:// 或 https://

在上面的例子中(实际上是现在遇到的所有例子),重复匹配元字符都是用在了前面的字符或者元字符上。例如, HTML 开发者在单词之间经常使用不间断空格来保证文本不会 wrap 。假设你需要定位所有重复的 HTML 不间断空格(并将其替换成其他字符),下面是个例子:

文本

Hello, my name is Ben Forta, and I am
the author of books on SQL, ColdFusion, WAP,
Windows  2000, and other subjects.

正则表达式
 {2,}

结果
Hello, my name is Ben Forta, and I am
the author of books on SQL, ColdFusion, WAP,
Windows  2000, and other subjects.

 

分析

“  " HTML 不间断空格的实体参考。模式“ {2,}”表示将匹配“  " 两次或者更多次。但是实际上并不是这样,为什么?“{2,}”实际上是指定了直接前面的字符的重复次数,在这里就是冒号,也就是说,“ ;;;;”将匹配,而“  ”并不会匹配。

 

子表达式的分组

上面的问题将我们带到了子表达式的主题。子表达式是一个大点的表达式,所有的部分组合起来作为一个实体。子表达式通过“ ( ”和“ ) ”括起来。

提示:“ ( ”和“ ) ”为元字符,如果要匹配这两个字符的话,需要使用转义“ \( ”和“ \) ”。

为了演示子表达式的用法,重新来看看上面的例子:

文本

Hello, my name is Ben Forta, and I am
the author of books on SQL, ColdFusion, WAP,
Windows  2000, and other subjects.

正则表达式
( ){2,}

结果
Hello, my name is Ben Forta, and I am
the author of books on SQL, ColdFusion, WAP,
Windows   2000, and other subjects.

分析

“( )”是一个子表达式并作为一个实体使用。在这里,“{2,}”将应用在整个子表达式上(而不仅仅是冒号)。这个模式很好的完成了工作。

 

下面是另外一个例子——用来定位 IP 地址的正则表达式。 IP 地址是由句号分隔的四组数字,如12.159.46.200等。因为每个部分的数字都可以为一个、两个或者三个数字字符,这个匹配模式可以表示为“\d{1,3}”。下面显示了这个例子:

文本

Pinging hog.forta.com [12.159.46.200]
with 32 bytes of data:

正则表达式
\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}

结果
Pinging hog.forta.com [12.159.46.200 ]
with 32 bytes of data:
分析

每个\d{1,3}的实例都匹配了 IP 地址的一个数字。四个数字则是被“ \. ”表示的“ . ”分隔开的。

 

“\d{1,3}\.”模式(不超过三个的数字和“ . ”)重复了三遍,因此可以使用重复操作来处理。下面是相同例子的另外一个版本:

文本
Pinging hog.forta.com [12.159.46.200]
with 32 bytes of data:

正则表达式
(\d{1,3}\.){3}\d{1,3}

结果
Pinging hog.forta.com [12.159.46.200 ]
with 32 bytes of data:

分析

此模式可以和前面的一样工作,但是语法并不相同。表达式“\d{1,3}\.”使用“ \( ”和“ \) ”括起来从而组成子表达式。“(\d{1,3}\.){3}”重复了次子表达式三次(也就是 IP 地址的前三个数字),接着的“\d{1,3}”匹配最后的数字。

注意:“(\d{1,3}\.){4}”并不是一个定位 IP 地址的正确方法。知道为什么这个模式会在上面的例子中失败吗?

提示:有些用户喜欢将部分表达式括起来成子表达式来增加可读性。上面例子中的模式还可以写为“(\d{1,3}\.){3}(\d{1,3})”。这种实践是完全合法的,而且对正则表达式的行为没有影响(可能会有解析上性能的开销,这取决于正则表达式具体的实现)。

使用子表达式来分组是很重要的。让我们再来看一个例子,尽管这个例子中根本不包括重复。下面的正则表达式试图在用户记录中找到一个年份:

文本
ID: 042
SEX: M
DOB: 1967-08-17
Status: Active

正则表达式
19|20\d{2}

结果
ID: 042
SEX: M
DOB: 19 67-08-17
Status: Active

分析

在这个例子中,此模式用来定位一个包含四个数字的年份。为了增加精确度,前两个数字只能为 19 20 。在第三章中解释过,“ | ”是一个 OR 操作符,所以“19|20”匹配“ 19 ”或者“ 20 ”,所以模式“19|20\d2 ”匹配任意以“ 19 ”或者“ 20 ”开始的四个数字字符的年份。但是很显然,此模式并不能工作,为什么?“ | ”操作符从左到右读取,将“19|20\d{2}”分析为要么 19 ,要么“20\d{2}”(以 20 开头的四个数字字符)。换句话说,此模式将匹配数字 19 和所有 20 开头的四个数字字符。所以, 19 也将会匹配。

解决方法是将“ 19|20 ”作为一个子表达式,就像下面一样:

文本
ID: 042
SEX: M
DOB: 1967-08-17
Status: Active

正则表达式
(19|20)\d{2}

结果
ID: 042
SEX: M
DOB: 1967 -08-17
Status: Active

分析

使用了子表达式,“ | ”知道如何选择选项。“(19|20)\d{2}”将会正确的匹配 1967 ,因为此模式能够匹配所有 19 20 开头的四个数字字符。如果时间更靠后一点,如一百年以后,需要增加一个 21 的匹配,模式可以修改为(19|20|21)\d{2}。

  • 尽管本章使用了正则表达式的分组,在第八章中将介绍子表达式的一个很重要的用途。

 

嵌套子表达式

子表达式可以嵌套。实际上,子表达式可以一层一层嵌套在子表达式内。

嵌套子表达式的能力将极大的发挥正则表达式的威力,但是这将使得正则表达式看起来比较复杂,难以阅读和分析,有时候会有些吓人。事实上,嵌套子表达式由于其复杂性很少在实际中用到。

为了演示嵌套子表达式的用法,我们再来看看查找 IP 地址的例子。这是上面用过的模式(一个重复三次的子表达式和最后一个数字):
(\d{1,3}\.){3}\d{1,3}

那么这个模式有什么问题呢?语法上是没有的。每个 IP 地址确实是由四个数字组成,每个数字可以包含三个数字字符,并通过句号分隔。这个模式是正确的,将匹配所有的合法 IP 地址。但是这不是此模式唯一可匹配的,非法的 IP 地址也将被匹配。

一个 IP 地址由四个字节组成,所以如12.159.46.200的 IP 地址实际上是四个字节的表示。因此, IP 地址的四个数字就有单字节的范围, 0 255 。这意味着 IP 地址中的每个数字都是小于 255 的。而上面的模式可以匹配 345 700 甚至是 999 ,而这些实际上都是非法的 IP 地址。

笔记:这是一个很好的教训。写出一个符合需求的正则表达式是很容易的,但是要写出一个匹配所有期望并拒绝所有不期望的情况的正则表达式是很难的。

指定合法值的范围是可以的,但是正则表达式匹配字符而并不知道匹配的字符是什么意思。数学上的计算并不是一个选项。

是否有选项呢?可能。为了构建正则表达式,你必须清晰的定义需要匹配什么而不匹配什么。下面定义了所有合法 IP 地址所需要满足的一种条件:

  • 所有的一位数和两位数
  • 三位数的第一位为 1
  • 如果三位数的第一位为 2 ,且第二位从 0 4
  • 如果三位数的前二位为 25 ,且第三位从 0 5

当定义了需要匹配的情况后,就比较容易实现可以工作的模式。下面是个例子:

文本
Pinging hog.forta.com [12.159.46.200]
with 32 bytes of data:

正则表达式
(((\d{1,2})|(1\d{2})|(2[0-4]\d)|(25[0-5]))\.){3}((\d{1,2})|(1\d{2})|(2[0-4]\d)|(25[0-5]))

结果
Pinging hog.forta.com [12.159.46.200 ]
with 32 bytes of data:

分析

这个模式明显可以工作,但是还是需要一些解释。使得此模式工作的原因是一系列的嵌套子表达式。首先从“(((\d{1,2})|(1\d{2})|(2[0-4]\d)|(25[0-5]))\.)”子表达式开始。包含了四个嵌套子表达式。“(\d{1,2})”可以匹配一位数和两位数,从 0 99 。“(1\d{2})”匹配了任何第一位为 1 的三位数,从 100 199 。“(2[0-4]\d) ”匹配数字从 200 249 。“(25[0-5])”匹配数字从 250 255 。每个子表达式都是通过“ | ”包括在另一个子表达式中(所以将匹配四个中的一种,而不是所有)。在数字范围之后是“ \. ”表示的“ . ”,然后这个系列括起来作为子表达式并重复三遍(使用“{3}”)。最后,数字范围被用来匹配最后一个 IP 地址的数字(没有了“ \. ”后缀)。由于将 IP 地址的四个数字都是限制在了 0 255 之间,所以此模式可以匹配所有的合法 IP 地址,而拒绝所有不合法的 IP 地址。

 

提示:像这样的正则表达式看起来很复杂。理解其的关键是分解、分析和理解每个子表达式。试着从子表达式开始从内到外,而不是从头开始一个一个字符解析。这将使得正则表达式看起来没有那么复杂。

 

小结

子表达式通过“ ( ”和“ ) ”定义,并用于对表达式进行分组。子表达式的一般用法是精确的控制重复的对象和 OR 条件的使用。如果需要的话,子表达式可以嵌套。

 

@ 亦歌亦行 http://searun.iteye.com

分享到:
评论

相关推荐

    精通正则表达式(第3版).pdf

    5. 回溯引用:回溯引用允许我们在正则表达式中使用之前已经匹配的子表达式,通常通过反斜杠后跟数字来实现。 6. 非捕获组:非捕获组用来指定一个组,但是不需要捕获它用于后续的引用,通常通过(?:pattern)来表示...

    shell正则表达式.zip

    在这个名为"shell正则表达式.zip"的压缩包中,包含了一个名为"正则表达式.jpg"的文件,推测这可能是一张图片,用于可视化地展示了一些常见的Shell正则表达式用法。 Shell正则表达式是基于Bash或其他Unix/Linux ...

    正则表达式系统教程 chm

     正则表达式的使用,可以通过简单的办法来实现强大的功能。为了简单有效而又不失强大,造成了正则表达式代码的难度较大,学习起来也不是很容易,所以需要付出一些努力才行,入门之后参照一定的参考,使用起来还是...

    正则表达式

     正则表达式的使用,可以通过简单的办法来实现强大的功能。为了简单有效而又不失强大,造成了正则表达式代码的难度较大,学习起来也不是很容易,所以需要付出一些努力才行,入门之后参照一定的参考,使用起来还是...

    正则表达式常用例子总结

    以下是我整理的一些正则表达式的常用例子,旨在帮助理解其基本用法和功能。 1. **基本匹配** - 匹配单个字符:`\d` 代表数字,`\w` 代表字母或数字,`\s` 代表空白符。 - 匹配范围:`[a-z]` 匹配小写字母,`[A-Z]...

    Python爬虫 Re库与正则表达式的细节解析

    上一篇中我们已经提到了,正则表达式使用 ‘’ 字符来使得一些普通的字符拥有特殊的能力(例如 \d表示匹配任何十进制数字),或者剥夺一些特殊字符的能力(例如 [ 表示匹配左方括号 ‘[’)。这会跟 Python字符串中...

    正则表达式 口诀 学习正则的朋友看看

    又受五笔字型字根表口诀“白手看头三二斤…”的启发, 试作“正则表达式助记口诀”又名“正则打油诗”,版本0.1,绝对原创,仿冒必究,:) 注:本文仅为学习正则时为了便于记忆而作,不能代替系统而全面的学习过程...

    C# 正则表达式经典分类整理集合手册第1/3页

    有一段时间,正则表达式学习很火热很潮流,当时在CSDN一天就能看到好几个正则表达式的帖子,那段时间借助论坛以及Wrox Press出版的《C#字符串和正则表达式参考手册》学习了一些基础的知识,同时也为我在CSDN大概赚了...

    regex_debug-html.rar_DEMO

    JavaScript中的正则表达式是通过创建RegExp对象或者使用字面量语法来创建的。例如: ```javascript var pattern = /pattern/g; // 字面量语法 var pattern2 = new RegExp('pattern', 'g'); // RegExp对象 ``` 在这个...

    高级Bash脚本编程指南--中文版(advance_bash_scriipt_progaming_guide).pd苹果脚本跟我学.pdff

    "高级Bash脚本编程指南--中文版(advance_bash_scriipt_progaming_guide).pdf" 和 "苹果脚本跟我学.pdf" 这两本书籍,提供了丰富的资源来帮助初学者和有经验的开发者提升Bash脚本编写技能。 Bash脚本是通过Shell...

    Java正则相关的Pattern和Matcher类及遇到的坑

    此篇文章是记录我在学习Java正则表达式时候学到的和遇到的坑。 先来说说 Matcher 里面的三个方法(取的结果以group()方法为例子) matches():整个匹配,只有整个字符序列完全匹配成功,才返回True,否则返回False...

    跟我学spring3(总共13章)6

    《跟我学Spring3》是针对初学者和有一定基础的开发者设计的一套全面、深入的教程,旨在帮助读者理解和掌握Spring3框架的核心概念和技术。本教程共计13个章节,覆盖了Spring3.0和3.x版本的主要特性。下面将详细阐述第...

    跟我学Nginx+Lua开发.pdf

    《跟我学Nginx+Lua开发》是一本针对Nginx与Lua结合使用的教程书籍,旨在帮助读者理解如何利用这两种技术进行高效且灵活的Web应用开发。Nginx作为一款广泛使用的高性能HTTP服务器和反向代理服务器,在处理高并发连接...

    跟我学写Makefile

    ### 跟我学写Makefile #### 概述与目的 本文档旨在通过系统而详尽的方式指导读者学习如何编写Makefile。Makefile是一种用于自动化构建过程的脚本文件,广泛应用于软件开发中,特别是在大型项目的编译过程中。通过...

    你必须知道的495个C语言问题(PDF)

    3.10 如果我不使用表达式的值, 我应该用++i 或i++ 来自增一个变量 吗? . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 15 3.11 为什么如下的代码int a = 100, b = 100; long int c = a * b;...

    跟我学Nginx + Lua开发

    安装过程中,可以加入特定的参数来安装内置模块,例如使用--with-http_realip_module安装取用户真实IP的模块,--with-pcre安装Perl兼容的正则表达式模块,--with-luajit集成LuaJIT模块,以及使用add-module添加...

    MATLABMYSQL爬虫-zhgd.m

    分析:每张图片都为 bimg 图片,作为正则表达式的筛选对象。注意:每种产品不一定是一张图片,需要对多张图片进行考虑 iii.简介:见后面图片 分析: 简介末尾都有 <!--/商品属性--> ,作为正则表达式的...

Global site tag (gtag.js) - Google Analytics