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

理解正则表达式,澄清一些概念[z]

    博客分类:
  • java
阅读更多
  作者简介:余晟,抓虾网工程师,毕业于东北师范大学,主修计算机,辅修中文,现居北京。曾任高级程序员,技术经理;从事过大量文本解析和数据抽取的工作。对程序语言、算法、数据库和敏捷开发都有兴趣,译有《精通正则表达式》(第3版)。 
 
       在这篇文章中,我们将尝试对正则表达式的相关概念加以梳理,详细讲解一些使用中容易出现问题的概念,并介绍一些目前大家不常用但非常有用的功能。
 
元字符(meta-character)
       正则表达式中的字符,可以粗略分为两类,文字(literal)和元字符(meta-character)。普通文字很好理解,例如字母『a』,出现在正则表达式中,就表示匹配字母‘a’,还有一类字符称为元字符,例如圆括号、方括号、花括号、美元符,等等,它们具有特殊的意义。
常用的元字符有:
 
点号『.』
       一般来说,点号可以匹配任意字符(但不包括换行符),但在特殊的匹配模式(下文将会介绍)下,点号可以匹配所有字符。
 
方括号『[』『]』
       表示字符组(character class),字符组可以罗列在单个位置能够出现的所有可能字符,否定型(negative)字符组罗列在单个位置不容许出现的所有字符。例如,『[a- z]』表示匹配成功时在此位置必须出现a-z中的一个字符,『[^a-z]』表示匹配成功时此位置不能出现a-z中的任意字符。
 
圆括号『(』『)』
       主要有三种功能:
  • 组合多选分支(Alternation)
  • 分组量词(quantifier)的作用对象
  • 捕获文本
       在某处可能匹配的多种选择(每种选择又可以是一个表达式),可以用圆括号和竖线来并行排列(但顺序有讲究,这里不展开),例如『(ab|cd|ef)』表示匹配成功时此处必须出现“ab”或“cd”或“ef”,而方括号表示的字符组(character class)只能并行排列多个字符(单个字符);
       量词限制某个元素在某位置重现的次数,常见的量词包括『*』(表示零次到任意多次)、『?』(表示零次到一次)和『+』(表示一次到任意多次)。量词只能修饰它之前紧邻的元素,因此『34*』能匹配“344444”,但不能匹配“343434”,如果希望匹配“343434”,必须将“34”两个字符单独分作一组,以量词修饰,也就是『(34)*』,我们也可以用括号+量词指定“出现的数字字符数目是3的倍数”之类的条件:『(\d\d\d)+』;
       括号的另一个功能是捕获文本,供反向引用(back-reference)或按编号访问:它会为已经匹配的文字提供一个编号,供表达式中靠后的元素使用,或是在匹配之后提取信息。例如,我们可以用『(\b\w+\b)\s+\1』匹配两个重复的单词,这里『\1』表示反向引用第一组括号内的表达式匹配的内容(其内容是无法事先决定的),也可以用一个正则表达式『(\d{2})/(\d{2})/(\d{4})』来匹配“07/31/2007”这样的日期,匹配完成之后,月、日、年的信息分别保存在第1、2、3号分组内,直接访问就可以提取出来。除这三种情况之外,应用括号之前必须仔细思考,到底有没有必要。正则表达式中最常见的缺陷就是滥用括号,在复杂的表达式中,这样会严重降低效率。
实际上,为了提高效率,许多系统提供了仅能分组而不支持反向引用的括号:『(?:regex)』,『(?:AB)+』表示“AB”必须出现一次以上,但之后无法作为一个分组加以引用。
 
花括号『{』『}』
       称为“区间”(interval),用数字指定之前元素的重现次数,『{1,3}』表示之前元素必须重现1~3次,『{,3}』表示之前元素至多能重现3次(也可以不出现),而『{3,}』表示之前元素至少必须出现3次。
 
^、$、\A、\Z
       这四个元字符不匹配任何字符,只匹配某个位置,依匹配模式的不同,『^』可以匹配整个字符串的开头,或是一行文本的开头,『$』可以匹配整个字符串的结尾,或是一行的结尾;而『\A』在任何情况下都匹配整个字符串的开头,『\Z』在任何情况下都匹配整个字符串的结尾。不要以为这四个元字符的应用场合有限,许多时候,它们可以用来“锚定”(anchor)正则表达式进行尝试的位置(例如,如果希望匹配的数据肯定是从一行的开头开始,就可以使用『^』),减少不必要的尝试,大大提高匹配的效率。
 
\w、\W、\d、\D、\s、\S、\b、\B
       这几个元字符也叫做“字符组简记法”,『\w』表示“单词字符”,通常相当于『[a-zA-Z0-9]』,而『\W』表示“非单词字符”,『\d』表示 “数字”,通常相当于『[0-9]』,而『\D』表示“非数字字符”,『\s』表示“空白字符”,可以匹配空格、指标符、换行符等等,而『\S』表示“非空白字符”,『\b』和『\B』则用来匹配位置,前者表示“单词分界位置”,也就是说一侧是单词字母,另一側不是单词字母的位置,而后者表示“非单词分界位置”,因此『\bbot\b』只能匹配单独出现的单词“bot”,绝对不会匹配单词“robot”。在某些系统中,这几个元字符会受到具体规定的限制,例如『\w』或许也能匹配希腊字符,『\d』或许也能匹配罗马数字,使用时应参阅具体的文档。
 
模式修饰符(mode-modifier)
       模式(mode)表示正则表达式在匹配时所采取的规则,模式修饰符用来设定模式。
       最常见的模式是Case-Insensitive Mode(简写为i),它表示“不区分大小写”。在默认情况下,CAT只能匹配单词CAT,如果采用不区分大小写的匹配模式,则『CAT』可以匹配“Cat”, “CAT”,“cat”等任意形式的“cat”。
       另一个常用的模式是Dot-match-all Mode(简写为s),它表示“点号通配”。在默认情况下,点号是不能匹配换行符的,此时,如果用『.*?』匹配一个 IMG tag,而这个tag的内容又跨越了两行,那么匹配是不会成功的。但如果使用“点号通配”模式,则点号可以匹配换行符,整个tag得以匹配。
       还有个常见的模式是Free-spacing and Comment Mode(简写为x),它表示忽略正则表达式中的空白字符(必须使用『\s』来表示空白字符,同时容许在正则表达式中加入注释)。
       最后介绍的模式是Multi-line Mode(多行模式,简写为m),又叫Enhanced Line-anchor Mode(增强的行锚点模式),在默认情况下,『^』和『$』只能匹配字符串的开头和结尾,但在此模式下,『^』和『$』可以匹配字符串当中的行开头/行结束,如果我们需要在包括多行文本的字符串中精确查找满足某条件的一行文本,就可以启用此模式,并使用『^』和『$』精确定位文本的两端。
       模式修饰符通常以flag(标志位)的形式指定,例如在Java中使用
       Pattern.compile("CAT", PATTERN.CASE_INSENSITIVE)
       在Python中使用
       re.compile("CAT", re.IGNORECASE)
       如果需要同时使用多个模式修饰符,可以以逻辑运算符“AND”来连接
       Pattern.compile("C(?#comment1)A(?#comment2)T", PATTERN.CASE_INSENSITIVE|PATTERN.COMMENTS)。
 
模式作用范围(mode-modified-span)
       这是与模式修饰符对应的概念。通常,我们需要显式地设定标志位,指定匹配模式,但此时模式是对整个表达式起作用的,无法进行更细的限制。而模式修饰范围则可以精确限定各元素的匹配模式,解决此类问题。
       不妨考虑这样的情况,一个正则匹配函数,接收两个参数,第一个参数为Tag的名字,第二个参数为匹配Tag内容中内容的表达式,而且,Tag名可以不分大小写,但内容匹配必须区分大小写。如果我们用一个匹配模式统摄整个表达式,必然无法完成要求,此时必须使用模式修饰范围,限定各个元素所使用的匹配模式。以Java为例,如果第一个参数为tagNameRegex,第二个参数为tagContentRegex,那么我们可以这样:
 
Pattern.compile(
"(?i)<" + tagNameRegex + ">" + ">(?-i)"
+ tagContentRegex
+ "(?i)tagNameRegex + ">" + ">(?-i)")
 
       如果传入的tagNameRegex是“td”,tagContentRegex是“\d{4}[a-z]3\d{3}”,则编译所用的表达式就是
『(?i)     (?-i)\d{4}[a-z]3\d{3}(?i)(?-i)』
       其中,我们在“     ”和“”两端分别以『(?i)』和『(?-i)』来开启/关闭不区分大小写的匹配模式,精确限定了此匹配模式的作用范围,而内部的tagContentRegex则不受影响,仍采用默认的匹配模式,如此,这个表达式能匹配“     4444azz333     ”,但不能匹配“     4444AZZ333”,这正是我们需要的。
       即使整个表达式采用同一种匹配模式,我们也可以使用模式修饰范围,例如『(?i)CAT』就表示,对整个表达式『CAT』进行不区分大小写的匹配。
       各种实现对于模式作用范围的支持和规定有所不同,使用时应参阅具体的文档。
 
环视(look-around)
       环视是很有意思的功能,它用来检查两端的字符,但不会把检查时匹配的字符加入匹配的最终结果。
       例如,表达式『\bJeff\b』只能匹配“Jeff”这个单词,如果我们需要精确匹配“Jeffrey”这个单词中的“Jeff”,就可以使用环视『Jeff(?=rey)』,后面的『(?=rey)』表示,如果匹配成功,“Jeff”之后必须出现“rey”。有的读者可能会说,那我直接使用『(Jeff)rey』,先找出来,再提取分组,不是一样吗?请注意,环视的对象又可以是正则表达式,『Jeff(?=(rey|erson))』就可以找到“Jeffrey”或“Jefferson”中的“Jeff”,这种灵活性是前一种做法无法提供的,而且,『(Jeff)rey』使用括号来捕获文本,效率有所降低。
       按照环视的方向不同,可以分为顺序环视(lookahead,表示从左向右检查)和逆序环视(lookbehind,从右向左检查);按照环视成立的条件不同,又可分为肯定环视(positive lookaround,只有在环视对象能匹配时才成功)和否定环视(negative lookaround,只有在环视对象无法匹配时才成功)。两者组合起来,就得到四种环视:
  • 肯定顺序环视
  • 肯定逆序环视
  • 否定顺序环视
  • 否定逆序环视
       所使用的标记也很好识别,『(?=Regex)』表示肯定顺序环视,『(?!Regex)』表示否定顺序环视,『(?<=Regex)』表示肯定逆序环视,『(?Regex)』表示否定逆序环视。
       在日常的HTML解析中,如果我们需要精确获得“src=...”中的资源地址(这里假定“src=...”的格式统一规范,等号两端没有空格,也没有引号),可以在表达式之前添加『(?<=).*?(?=< /B>)』来精确匹配“...”之中的内容。在这两个例子中,当然也可以使用匹配-括号提取的办法,但使用环视的效率更高,也更切合程序的本意。
       环视还可以多个连用,我曾遇到过这样的情形:有站点siteA.com,需要在Apache的配置文件中设定重定向规则,以一个正则表达式匹配除sub1, sub23之外的所有子域名(注意,是匹配所有子域名),首先我想到的是
『^[^.]*(?<!---->
       但这行不通,因为多数系统都不容许在逆序环视中使用变长表达式(只有.NET容许),所以必须连用多个逆序环视
       『^[^.]*(?<!---->
       这样便可以了。
 
固化分组(Atomic-grouping)
       回溯(back-tracking)是匹配过程中常见的现象,如果用『.*ab』来匹配“123456ab”,『.*』首先会匹配整个字符串,之后轮到『ab』,『.*』需要“依次释放”之前匹配的两个字符,供『ab』匹配,整个表达式才能匹配成功。这样“依次释放”的过程,就叫做回溯。但有时回溯完全是徒劳的,例如我们用『\w+:』来匹配“Subject”。因为字符串中不存在冒号,匹配肯定会失败,但引擎仍然必须依次回溯,最终得出失败的结果,而我们知道,『\w』“释放”的字符,:肯定无法匹配。此时可以使用固化分组,将『\w+』匹配的内容“固定”下来,禁止回溯:
『(?>\w+):』
       这样报告匹配失败的速度就提高了许多倍,如果字符串很长,使用固化分组就能节省大量的时间。
固化分组的另一个用途是精确控制匹配,防止不期望的匹配。例如,表达式『<(\w+).*?』,本意是匹配对称的tag之间的文本,但它会错误地匹配“ <link>…”。如果使用固化分组『<((?>\w+)).*?』,就能解决这个问题。
 
总结
       最后,我们来总结一下这篇文章中介绍的概念:
  • 元字符是不同于普通文字的字符,表示特殊的含义,一类元字符用来匹配具体的字符(例如点号、『\w』、『\d』之类),它们可以很方便地表示各类字符组,增强表达式的可读性;另一类用来匹配位置,锚定表达式,运用得当的话,不但能增加匹配的准确程度,还能提升匹配的效率;
  • 模式修饰符用来规定匹配进行的规则,例如进行不区分大小写的匹配,规定点号可以匹配换行符之类,多个模式修饰符可以同时使用;
  • 模式修饰范围用来规定表达式中各个部分所使用的匹配模式,也就是说,一个表达式内的各个部分可以有各自的匹配模式,而不会互相冲突,这样我们能对匹配施加更精确的控制;
  • 环视用来判断某个位置的左/右侧的文本是否满足要求,但环视结构中的表达式匹配的文字并不会作为最终的匹配结果,环视也可以用来准确定位;
  • 固化分组可以禁止回溯,也是对匹配过程施加控制的一种手段,巧妙利用固化分组,可以大大提高匹配效率,或是进行更准确的匹配。
分享到:
评论

相关推荐

    vb正则表达式实例(正则表达式测试程序)

    这个“vb正则表达式实例”很可能是为了帮助开发者测试和理解正则表达式的工作原理而设计的一个应用程序。下面将详细探讨正则表达式的基本概念、在VB.NET中的应用以及如何使用它们进行文本匹配。 1. 正则表达式基础 ...

    PB实现的正则表达式

    在IT领域,正则表达式(Regular Expression,简称regex)是一种强大的文本处理工具,它能够进行复杂的模式匹配、查找、替换等操作。在本话题中,我们将探讨如何使用PowerBuilder 11.5这一经典的开发环境来实现正则...

    正则表达式转换工具

    1. **正则表达式基本概念** - 元字符:如`.`, `*`, `+`, `?`, `{}`, `[]`, `\`, `^`, `$`等,它们具有特殊含义,在正则表达式中用来定义模式。 - 字符类:例如`[abc]`,匹配其中任意一个字符。 - 量词:`*`, `+`,...

    pb 使用正则表达式源码pbregexp

    源码学习有助于深入理解正则表达式在PB环境下的工作原理,也可能为自定义或扩展组件功能提供可能。 总的来说,pbregexp组件为PowerBuilder开发者提供了一种强大而灵活的工具,帮助他们更高效地处理文本数据。通过...

    正则表达式测试工具C#版(src)

    - 调试技巧:源码中可能会有调试正则表达式的辅助逻辑,帮助开发者更好地理解和调试自己的正则表达式。 通过深入研究这个C#版的正则表达式测试工具源码,开发者不仅可以巩固正则表达式的基础知识,还可以提升在C#...

    VC、VC++,MFC 正则表达式类库

    正则表达式类库则为VC++和MFC的开发者提供了对正则表达式功能的支持。 "VC、VC++,MFC 正则表达式类库"指的是在MFC中实现或集成的正则表达式处理模块。这个库通常包含一系列的类和函数,允许程序员编写符合特定模式...

    正则表达式必知必会v_1.0.pdf

    正则表达式的语法比较容易理解,但学习正则表达式的主要困难在于如何灵活运用这些规则来达到查找和替换的目的。 匹配单个字符 在正则表达式中,可以使用句点(.)来匹配任意单个字符。句点在正则表达式中被称为元...

    C语言正则表达式库

    C语言正则表达式库是用于在C编程环境中处理和匹配正则表达式的软件库。这个库名为PCRE(Perl Compatible Regular Expressions),正如其名,它与Perl语言中的正则表达式语法高度兼容,提供了丰富的功能和强大的匹配...

    正则表达式大全.docx

    以下是一些常见的正则表达式模式及其用途: 1. 用户名验证:`/^[a-z0-9_-]{3,16}$/` - 这个正则表达式用于检查用户名是否合法,它只接受小写字母、数字、下划线和破折号,且长度在3到16个字符之间。 2. 密码验证:...

    正则表达式大全 - 收集的最常用正则表达式

    以下是一些常见的正则表达式及其用途: 1. 匹配中文字符:`[u4e00-u9fa5]` - 这个正则表达式用于匹配所有中文字符,包括简体和繁体。它基于Unicode范围,u4e00到u9fa5涵盖了大部分常用汉字。 2. 匹配双字节字符:`...

    Java使用正则表达式提取XML节点内容的方法示例

    正则表达式是指一个模式串,它由一些特殊的符号和字符组成,用于描述一个字符串的模式。Java中提供了对正则表达式的支持,使得开发者可以使用正则表达式来匹配和提取字符串中的内容。 二、XML节点内容提取方法 在...

    正则表达式在数据库查询中的应用

    ### 正则表达式在数据库查询中的应用 #### 引言 在数据库管理与应用程序开发中,查询数据是一项常见的任务。传统的SQL查询虽然强大,但在处理复杂查询时可能存在一定的局限性,尤其是涉及到文本数据的模式匹配时。...

    易语言正则表达式文本替换

    易语言是一种专为中国人设计的编程语言,它以简体中文作为编程语句,降低了编程的门槛,使得更多的人能够理解和使用编程...通过深入理解正则表达式和易语言的相关函数,开发者可以灵活地处理各种复杂的文本处理任务。

    正则表达式自动生成器 V2.0.0.1 官方多语版

    这款V2.0.0.1版本的正则表达式自动生成器提供了一些关键功能,包括: 1. **向导式界面**:对于不熟悉正则语法的用户,该工具可能提供了一个友好的向导,逐步引导用户构建复杂的正则表达式。 2. **实时预览**:用户...

    正则表达式生成工具,正则表达式生成工具

    3. **语法提示**:提供正则表达式语法的详细提示和参考,帮助用户理解和学习正则表达式中的各种符号和结构,如点号(.)、星号(*)、加号(+)、问号(?)、括号(())、管道符(|)等。 4. **测试集**:允许用户...

    Lucene 使用正则表达式

    ### Lucene 使用正则表达式 #### 知识点概览 1. **Lucene简介** 2. **正则表达式(regex)在Lucene中的应用** 3. **regexQuery详解** 4. **示例代码解析** 5. **索引创建与查询流程** 6. **正则表达式的语法** #### ...

    三目运算符+正则表达式

    在编程世界中,三目运算符和正则表达式是两个非常重要的概念,它们各自扮演着独特的角色,同时也常被结合起来使用,以提高代码的简洁性和效率。让我们深入探讨这两个主题。 首先,三目运算符,也称为条件运算符,是...

    正则表达式验证工具,正则表达式校验工具

    正则表达式验证工具 V1.0 本软件主要用于检测正则表达式是否正确。 运行环境:本软件为绿色软件,无需安装,但需要Microsoft .NET Framework 4 支持,如果没有请前去下载(下载路径:...

Global site tag (gtag.js) - Google Analytics