首页 > 代码库 > 刨根究底正则表达式之一——正则表达式简介
刨根究底正则表达式之一——正则表达式简介
声明:
本系列文章的主要参考书有:
《精通正则表达式》英文版及中文版 作者: Jeffrey E·F·Friedl 译者:余晟 电子工业出版社 2012-07
《正则指引》作者:余晟 电子工业出版社 2012-05
《正则表达式必知必会》作者:Ben Forta 译者:杨涛 人民邮电出版社2015-01
《冒号课堂:编程范式与OOP思想》作者:郑晖 电子工业出版社 2009-10
同时,还参考了网上的大量资料,除了少部分资料由于未作大量修改(但基本上也有少量修改,因为网上文章随意性较大,很多明显的笔误或前后矛盾之处,如若不改反而让人迷糊)而标明了原作者和出处之外,其余由于基本上已按自己的理解作了大量改写,因此没有再一一予以说明,在此对原文作者表示歉意并感谢。
另外,文中图片小部分来自网络,大部分为本人制作,也不再一一说明,在此对原图作者表示歉意并感谢。
再者,虽然本系列文章会在开篇简单介绍正则表达式的一些基础知识,但主要限于本系列文章所想强调的要点,因此本系列文章并不适合用于入门。
若你是对正则表达式没有任何概念的初学者,建议至少先阅读网上备受推崇的《正则表达式30分钟入门教程》,时间允许的话最好再阅读《正则表达式必知必会》(才130多页,写得非常基础,快的话一天可轻松读完)。这样在建立了对正则表达式的基本认识之后,再来阅读本系列文章,才更为合适。
最后,文中若有错漏,还请直接招呼板砖,不用客气??
正则表达式简介
一、正则表达式概念
一)先从“通配符”说起
1.
对于初学者而言,正则表达式,仅从字面上来说不太好理解。但实际上,您可能早已经使用过了某些正则表达式的功能,只是自己还没有意识到而已。
例如,您很可能使用过?和*这两个通配符来查找硬盘上的文件。?通配符匹配文件名中的单个字符,而*通配符匹配零个或多个字符。像“data?.dat”这样的匹配模式将可以匹配查找到下列文件名:
data1.dat
datax.dat
dataN.dat
使用*字符代替?字符可以扩大所能找到的文件数量。比如“data*.dat”可以匹配下列所有文件名:
data.dat
data1.dat
data12.dat
datax.dat
dataXYZ.dat
2.
因此,所谓“通配符”,即“通用匹配字符”,就是用某个通用字符按事先所规定的规则来查找匹配某些常规字符,从而实现“以一对多”(或“以一代多”)、“以简对繁”(或“以简代繁”)地简化、抽象化、通用化用来进行查找匹配的表达式的目的。
然而,尽管使用“通配符”的匹配查找方法很有用,但它的功能还是非常有限的。和通配符类似,正则表达式也是用来进行文本匹配查找的工具。只不过相比通配符而言,正则表达式更为抽象化、通用化,功能也更为强大、更加灵活,能够更为精确地表达匹配条件(即匹配规则),当然也就更复杂,更难以学习和掌握。
(笨笨阿林原创文章,转载请注明出处)
二)正则表达式概念
1.
正则表达式,又称正规表示法、常规表示法(Regular Expression,在代码中常简写为regex、regexp或RE),计算机科学的一个概念。
正则表达式是一种字符串的匹配模式,描述的是某一类字符串的共同特征。
2.
所谓模式,就是模板样式或模具样式。正如符合某种样式的模板或模具,可以用来生产符合这种样式的同一类产品一样;反过来,也可以用某种样式的模板或模具,来检验或框定哪些产品才是符合这种样式的同一类产品。
正则表达式正是类似于这样的模板或模具,用来检验或框定哪些字符串是符合正则表达式所描述的字符串共同特征的同一类字符串;而这个检验或框定的过程,就称之为匹配。
3.
我们平时所使用的自然语言中,可以用“漂亮”、“坚固”、“挺拔”等高度抽象性词语来描述事物的共同特征一样,一个正则表达式正是某一类字符串的高度抽象,用来描述这类字符串的共同特征。也就是说,一个正则表达式代表了某类字符串的一个集合,而正则表达式相当于对该字符串集合的特征性质描述。(注:集合的常用表示方法有元素列举法、特征性质描述法和图示法。)
正则表达式还可看作是对字符串操作的一种逻辑公式,其构造方法和创建数学表达式的方法差不多,也就是用普通字符(如字母a到z、数字0到9等)和事先定义好的一些特定字符(专业术语称之为元字符),以及这些字符的组合,组成一个特定的规则字符串。而所谓特定的规则,即是正则;因此特定的规则字符串,即是正则表达式。
这些“特定的规则”,从被匹配的字符串的角度上来看,可以认为描述的是某一类字符串的共同特征;而从正则表达式的角度上来看,也可以认为表达的是一种匹配规则(或称过滤逻辑)。
4.
因此,正则表达式是一种特殊的字符串(即正则表达式字符串,往往直接简称为正则表达式或正则式),用来描述、匹配、过滤符合某些特征的其它字符串(即输入字符串、源字符串、被测试的字符串、被匹配的字符串,往往直接简称为字符串)。
说某个正则表达式匹配某个字符串,通常是指这个字符串的全部或一部分或几部分分别符合或者说满足正则表达式所描述的字符串特征;也可以说是指这个字符串的全部或一部分或几部分分别符合或者说满足正则表达式所规定的匹配条件或匹配规则。
(笨笨阿林原创文章,转载请注明出处)
5.
而从正则表达式作为一种编程语言的角度上来看,正则表达式的基本语法结构与一般高级编程语言差不多,主要就是顺序(即连接)、选择(即分支)、循环(即重复)三种,其他都是这三者的组合,再加上一些语法糖。
再更进一步地,从正则表达式作为一个声明式编程范式的领域特定语言DSL的角度来讲,正则表达式的顺序、选择、循环这三种基本语法结构是非常简洁、紧凑的(这几乎是声明范式DSL的基本特点,而正则表达式将这一点体现得尤为淋漓尽致)。其中,连接无需通过元字符表示,选择通过元字符“|”表示,而循环则通过元字符“*”、“+”或“{n,m}”表示。这三种基本语法结构在使用时,直接进行声明式描述即可,无需通过复杂的语句来进行算法设计
事实上,还可从编程语言操作符(即运算符)的角度来理解,其中,“*”、“+”或“{n,m}”是单目后缀操作符,“|”是双目中缀操作符,连接其实也是双目中缀操作符,不过是隐含的(即隐式的,因为连接是三种基本语法结构中最常用的,所以设计为隐式操作符最为合理)。
二、正则表达式功能
1.
一般而言,典型的简单搜索和替换操作,可通过直接提供与预期的搜索结果相匹配的字面文本来实现。虽然这种方法对于文本执行简单的、静态的搜索和替换任务可能已经足够了,然而却缺乏足够的灵活性和动态性。
若通过使用正则表达式,则可以:
-
- 查找文本
查找符合某一正则表达式的文本,尤其是查找符合某一正则表达式的非固定文本,比如查找符合某一种模式(甚至长度不定)的文本。
-
- 提取文本
可以查找字符串内符合某个文本模式的文本(子字符串),然后将其提取出来以备他用。
-
- 验证文本
所谓验证文本,是指检査文本能否完全由正则表达式匹配,主要用来测试和保证数据文本的合法性。
例如,可以测试输入字符串,以查看字符串内是否出现电话号码模式(比如0XXX-XXXXXXXX这样的模式:必须为0开头,接着3位数字、短横杠、8位数字)。
-
- 替换文本
可以使用正则表达式所表示的文本模式来识别、匹配文档中符合该文本模式的所有文本(即符合该文本模式的文本的集合),而不只是识别、匹配某个特定的、确切的文本(比如0XXX-XXXXXXXX就是电话号码模式,而0755-88888888就是某个特定的、确切的电话号码),然后可以完全删除匹配该文本模式的所有文本(相当于用空字符串替换)或者用其他文本逐一进行替换。
-
- 切分文本
切分也是正则表达式的常见操作之一,切分操作一般以正则表达式匹配的文本作为间隔,将字符串切分成多个片段(即子字符串)。
2.
显然,通过使用文本模式,正则表达式相比较于直接使用固定的、明确的字面文本进行简单的、静态的搜索和替换,更为灵活,也更具有动态适应性。而且,正则表达式同样也可以使用字面文本进行简单的、静态的搜索和替换(当然,这有点大材小用了,效率也比直接搜索和替换更低,因此,字面文本的直接搜索和替换,不推荐使用正则表达式)。
因此,正则表达式的熟练运用,是文本处理人员,尤其是编程人员的必备技能。其强大的功能、快捷的速度,一旦掌握,你将会既叹服于心,又享受其中。
三、正则表达式简史
1.
正则表达式的“祖先”可以一直追溯至对人类神经系统如何工作的早期研究。Warren McCulloch和Walter Pitts这两位神经生理学家在20世纪40年代研究出用一种数学方式来描述神经网络。
1956年,一位叫Stephen Kleene的数学家在McCulloch和Pitts早期工作的基础上,发表了一篇标题为《神经网络事件表示法和有穷自动机(Representation of events in nerve nets and finite automata)》的论文,引入了正则表达式的概念。正则表达式就是用来描述他称为“正则集合Regular Sets”的表达式,这就是“正则表达式”这个术语的来源。
2.
随后,大名鼎鼎的Unix之父——Ken Thompson于1968年发表了文章《正则表达式搜索算法(Regular Expression Search Algorithm)》,并且将正则表达式这一符号系统引入了他自己开发的编辑器qed以及之后的编辑器ed中,然后又被移植到了大名鼎鼎的文本搜索工具grep中。自此,正则表达式被广泛应用到各种Unix系统或类Unix系统(如Mac系统、Linux系统)的工具中。
由于正则表达式异常强大而实用的功能,越来越多的语言和工具引入了正则表达式。不过遗憾的是,始终没有确立正则表达式方面的标准,导致各语言与工具中的正则表达式虽然功能上大体类似,但细微差别仍然不少。于是,诞生于1986年的POSIX开始进行标准化的尝试。
(笨笨阿林原创文章,转载请注明出处)
3.
POSIX,是Portable Operating System Interface for uniX(可移植Unix操作系统接口)的缩写。POSIX是一系列规范,定义了Unix操作系统应当支持的功能,其中也包括正则表达式的规范。
因此,Unix系统或类Unix系统上的大部分工具,如grep、sed、awk等,均遵循该标准。遵循POSIX正则表达式规范的这些语言和工具中的正则引擎,往往习惯将它们称之为POSIX流派的正则引擎。
4.
之后,1988年6月,Larry Wall开发的Perl语言发布第2版,其中所引入的正则表达式引擎大放异彩。Perl 2的正则表达式引擎源于Henry Spencer编写的regex的增强版。之后不断改进,影响越来越大。于是在此基础上,1997年又诞生了PCRE——Perl兼容正则表达式(Perl Compatible Regular Expressions)。
pcre是一个由Philip Hazel开发的、为很多现代语言和工具所普遍使用的Perl正则表达式兼容引擎,现已成为除了Unix上的工具所遵循的POSIX标准之外的其他大部分语言和工具所隐然遵循的另一个事实上的标准。因此,往往习惯将这些Perl正则表达式兼容引擎称之为PCRE流派的正则引擎。
POSIX流派与PCRE流派是目前正则表达式引擎流派中的两大最主要的流派。
之后,正则表达式在各种计算机语言或各种应用领域进一步得到了更为广泛而普遍的应用和发展。
Perl语言之父 Larry Wall
四、正则表达式流派
1.
如前所述,目前正则表达式主要有两大流派(Flavor):POSIX流派与PCRE流派。
1) POSIX(Portable Operating System Interface for uniX)流派
POSIX是一系列规范,定义了UNIX操作系统应当支持的功能,其中也包括正则表达式的规范。POSIX规范的正则表达式流派是PCRE之外的另一大流派。
POSIX规范定义了正则表达式的BRE(Basic Regular Expression基本正则表达式)和ERE(Extended Regular Express扩展正则表达式)两种标准。早期,BRE与ERE的区别主要在于:
不过,后来随着BRE与ERE逐渐相互融合,现在的BRE和ERE(包括GNU改进的GNU BRE和GNU ERE)在功能特性上并没有太大区别,主要的差异是在元字符的转义上。
在遵循POSIX规范的UNIX/LINUX系统上,vi/vim、grep和sed遵循POSIX规范的BRE标准,egrep、awk则遵循ERE标准。这些UNIX/LINUX系统常用工具的正则表示法与PCRE对比如下:
注1:vim中的\?和\=都表示匹配0或1个前面的子表达式,但\?不能在反向查找的“?”命令中使用。
注2:vim中的右花括号}之前可以不加反斜杠,也可以加反斜杠,比如:\{n,m\}。
注3:PCRE中常用\b来表示“单词的起始或结束位置”,但Linux/Unix的工具中,通常用\<来匹配“单词的起始位置”,用\>来匹配“单词的结束位置”,sed中的\y可以同时匹配这两个位置。
2) PCRE(Perl Compatible Regular Expression)流派
目前大部分常用编程语言中常见的正则表达式语法,其实都源于Perl。其中,PCRE就是从Perl衍生出来的最为著名的一个流派,\d、\w、\s之类的字符组简记法,是这个流派的显著特征。
不过,虽然PCRE是从Perl语言中衍生出来的,但与Perl语言的正则表达式还是有一些细微差异的,比如PHP的preg(Perl Regular Expression)与Perl的差异可看这里。
(注:PHP支持两种不同的正则引擎:ereg与preg,ereg全称为Extended Regular Expression,属于POSIX ERE,详见下文;ereg由于功能方面的不足,已经逐渐被preg替代了,ereg将在未来被废弃。因此,若非特别说明,后文中当提到PHP正则引擎时,默认指的是PHP preg正则引擎。)
考虑到目前绝大部分常用编程语言所采用的正则引擎基本属于PCRE流派,因此,本系列文章将以PCRE流派为主、以POSIX流派为辅进行介绍;文中有关各语法元素的解释,若非特别说明,均以PCRE流派为准。
2.
另外,如前所述,当我们在介绍正则表达式的流派时,与Perl正则规范相兼容(包括直接兼容与间接兼容)的流派习惯用PCRE来称呼。
而本系列文章在介绍与Perl正则规范直接兼容(但并非完全兼容)的语言或正则库或工具程序,比如Perl、PHP preg、PCRE库时,一般称之为Perl系;与之对应的还有间接兼容的Java系(包括Java、Groovy、Scala等)、.Net系(包括C#、VB.Net)、Python系(包括Python2和Python3)、JavaScript系(包括原生JavaScript和扩展库XRegExp)等等。
也就是说,Perl系、Java系、.Net系、Python系、JavaScript系(另外还有Ruby、C++Builder、Delphi等)均属于PCRE流派,但与Perl的兼容性(即兼容程度)各有不同。其中,Perl系兼容性最好,虽然PHP preg与PRCE库并非与Perl完全兼容,但基本兼容,因此属于直接兼容;而其他语言或工具相对Perl系而言,与Perl的兼容性较差,则属于间接兼容。
(笨笨阿林原创文章,转载请注明出处)
(未完待续)
本系列文章上一篇为:刨根究底正则表达式之零——前言
【预告:下一篇为正则表达式基础与八大原则简介,敬请关注!】
刨根究底正则表达式之一——正则表达式简介