正则表达式现在应用十分的广泛,出现在UNIX/Linux相关的各种领域和多种编程语言里。Linux系统管理员应该熟悉这些表达式。从常见的shell命令到大名鼎鼎的Perl语言再到当前非常流行的PHP,它都扮演着一个重要的角色。甚至windows的命令行控制台也支持正则表达式。
可以说,它是学好Linux/UNIX必需掌握的一个知识点,否则你连Linux的启动脚本都读不懂。偏偏它又的确有点晦涩难懂,而且相关的资料又大部分是英文,更为它的学习增加了几多困难。即使有些中文的翻译资料,不同的译者对一些术语的译法也五花八门,读着让人平添困惑。为此,我决定为它写一个简明教程,尽量可以覆盖正则表达式涉及到的各主要概念。
我并不想把本文写成一本详细的正则表达式语法手册,事实上,这些手册已经存在了,不过读起来比较难懂。我希望的是在完成本教程后,你可以比较轻松的读懂各种工具的正则表达式语法手册并可以迅速上手,不过要用好正则表达式,可不是一篇短短的教程可以解决的,那是无数实践练习的结果。但是,本文的最后一部分对于正则表达式的编写提出了一些原则性的建议,学习一下这些正则表达式应用先驱者的经验会让我们在今后的实践中少走一些弯路。
正则表达式是英文“regular expressions”的译文,它的产生据说可以追溯到“神经网络”等比较高深的理论。那么什么是正则表达式呢?
正则表达式是从左向右去匹配目标字符串的一组模式。大多数字符在模式中表示它们自身并匹配目标中相应的字符。举个最简单的例子,模式“The quick brown fox”匹配了目标字符串中与其完全相同的一部分。
前面已经提过,正则表达式被许多植根于UNIX/Linux的工具采用,可是这些工具的正则表达式语法并不完全相同,它们中的一些对正则表达式语法的扩展并不被其它工具识别,这也为正则表达式的使用增加了难度。因此,当你在一个具体的环境中使用正则表达式时,你还要先看一下目标环境支持的语法范围,以确保你的正则表达式被正确的解析。
在本文中列举的例子里,我们用正斜线“/”做为模式的定界符(delimiter),一个模式用下面这种格式表示:
/[A-Z]+(abc|xyz)*/i
本文将较详细的阐明下面这些正则表达式概念:模式修正符(modifier),元字符(Meta-characters),子模式(subpatterns)与逆向引用(Back references),重复(Repetition)和量词(quantifiers),断言(Assertions),注释,正则表达式中的递归,最后我介绍一款方便学习正则表达式的工具并介绍一些正则表达式编写的思路。
正则表达式的模式修正符(modifier)
正则表达式的模式修正符主要用来限定模式与目标字符串的匹配方式,例如是否需要大小写敏感的匹配,是单行模式还是多行模式。修正符中的空格和换行被忽略,其它字符会导致错误。下面列举一些常见的模式修正符。注意,模式修正符是区分大小写的。
i:非大小写敏感模式,:如果设定此修正符,模式中的字符将同时匹配大小写字母。
m:多行模式,当设定了此修正符,“行起始”和“行结束”除了匹配整个字符串开头和结束外,还分别匹配其中的换行符的之后和之前。
s:单行模式,如果设定了此修正符,模式中的圆点元字符(.)匹配所有的字符,包括换行符。没有此设定的话,则不包括换行符。
对于多行模式和单行模式,一个容易让初学者迷惑的地方是这两者并不向字面上那样是互斥的。事实上,它们只是分别定义了英文句点(.)、音调符(^)和美元符($)这三个元字符的匹配方式,因此,单行模式与多行模式的修正符可以同时使用。
x:如果设定了此修正符,模式中的空白字符除了被转义的或在字符类中的以外完全被忽略,在未转义的字符类之外的 # 以及下一个换行符之间的所有字符,包括两头,也都被忽略。它使得可以在复杂的模式中加入注释。我们会在后面的部分更详细的讲解正则表达中的注释。
模式修正符还有很多,这里不再一一列举。我们会结合后面的内容介绍一些其它的模式修正符。不同的工具也可以添加自己的模式修正符,不过上面几最为常见。
模式修正符通常跟在模式定义结束符的后面,例如下面例子中模式最后的“i”字符。/[A-Z]+(abc|xyz)*/i,这时此修正符会对整个匹配模式起作用。模式修正符也可以在模式内部通过包含在 "(?" 和 ")" 之间的修正符字母序列来实现。例如,(?im) 设定了不区分大小写,多行模式。也可以通过在字母前加上减号来取消这些选项。例如组合的选项 (?im-s),设定了不区分大小写和多行模式,并取消了单行模式。如果一个字母在减号之前与之后都出现了,则该选项被取消设定。
注意,如果(?im-s)出现在一个子模式内(被另一对小括号包含)会把模式修正符的作用局限在该子模式中。
Linux正则表达式不仅仅是用于各种语言的编程,系统管理员也必须得熟悉。前面我介绍了模式修正符,现在我们继续介绍,它是学好Linux/UNIX必需掌握的一个知识点,否则你连Linux的启动脚本都读不懂。
Linux正则表达式的元字符(Meta-characters)
Linux正则表达式的威力在于其能够在模式中包含选择和循环。它们通过使用元字符来编码在模式中,元字符不代表其自身,它们用一些特殊的方式来解析。
有两组不同的元字符:一种是模式中除了方括号内都能被识别的,还有一种是在方括号内被识别的。如果想在模式里包含一个元字符本身,就需要用到转义符号,正则表达式常用反斜线“\”作为转义字符使用,为了匹配“\”本身,你需要输入两个“\”,向这样“\\”。当然,这个符号本身也是一个元字符。
方括号之外的元字符有这些:
\
有数种用途的通用转义符
^
断言目标的开头(或在多行模式下行的开头,即紧随一换行符之后)
$
断言目标的结尾(或在多行模式下行的结尾,即紧随一换行符之前)
.
匹配除了换行符外的任意一个字符(默认情况下)
[
字符类定义开始
]
字符类定义结束
|
开始一个多选一的分支
(
子模式开始
)
子模式结束
?
扩展 ( 的含义,我们已经在介绍模式修正符里看到过它的使用。它也可以是 0 或 1 数量限定符,以及数量限定符最小值
*
匹配 0 个或多个的数量限定符
+
匹配 1 个或多个的数量限定符
{
最少/最多数量限定开始
}
最少/最多数量限定结束
模式中方括号内的部分称为“字符类”。字符类中可用的元字符为:
\
通用转义字符
^
排除字符类,但仅当其为第一个字符时有效
-
指出字符范围
在这里,最值得一提是“\”这个元字符。之所以重点对它进行讲解是因为这一个元字符有多种不同的用法,在不同情况下代表不同的含义,而且使用频率非常高,是个很容易让人迷惑的地方。
第一种用法前面我们已经提过,是作为通用转义字符使用,如果其后跟着一个非字母数字字符,则取消该字符可能具有的任何特殊含义。此种将反斜线用作转义字符的用法适用于无论是字符类之中还是之外。例如“\\”代表一个单独的反斜线“\”。
第二种用途提供了一种在模式中以可见方式去编码不可打印字符的方法。模式中完全可以包括不可打印字符,除了代表模式结束的二进制零,例如,可以用“\a”代表alarm,即 BEL 字符(0x07),或用“\cx”代表"control-x",其中 x 是任意字符。当然,这种方法表示的不一定非得是不可打印字符,实际上,可以用“\xhh(十六进制代码为 hh 的字符)”和“\ddd(八进制代码为 ddd 的字符)”来以编码的形式表达任何单字节字符,例如“\040”可以用来表示空格。
反斜线的第三个用法是指定通用字符类型,这些字符类型序列可以出现在字符类之中和之外。每一个匹配相应类型中的一个字符。如果当前匹配点在目标字符串的结尾,以上所有匹配都失败,因为没有字符可供匹配。有以下这些常见的通用字符类:
\d 任一十进制数字
\D任一非十进制数的字符
\s任一空白字符
\S任一非空白字符
\w任一“字”的字符
\W任一“非字”的字符
反斜线的第四个用法是某些简单的断言,关于断言的讨论我们放在后面,这里先不加讨论。
反斜线的最后一个用法是逆向引用。关于逆向引用,我们会在后面讨论逆向引用的部分来做进一步的讨论。
我们已经看到,反斜线的众多用法,其中一些涉及到了以后才讲的内容。我们在模式中遇到反斜线时一定要注意它具体是哪一种用途以免疑惑。
另外两个方括号也是非常重要的元字符,左方括号开始了一个字符类,右方括号结束之。单独一个右方括号不是特殊字符。字符类匹配目标中的一个字符,该字符必须是字符类定义的字符集中的一个;除非字符类中的第一个字符是音调符(^),此情况下目标字符必须不在字符类定义的字符集中。如果在字符类中需要音调符本身,则其必须不是第一个字符,或用反斜线转义。例如,[^A-Z]表式非大写字符。
Linux正则表达式的模式修正符与元字符在前面两篇我们已经介绍了,因为网上现有的正则表达式资料都对这部分都有详细的介绍和众多的例子,如果觉得对前一部分缺乏了解可以参看这些资料。本文希望可以尽可能多涉及一些较高级的正则表达式特性。
在本文里,我们主要介绍Linux正则表达式的子模式(subpatterns),逆向引用(Back references)和量词(quantifiers),其中重点介绍对这些概念的一些扩展应用,例如子模式中的非捕获子模式,量词匹配时的greedy与ungreedy。
子模式(subpatterns)与逆向引用(Back references)
正则表达式可以包含多个字模式,子模式由圆括号定界,可以嵌套。这也是两个元字符“(”和“)”的作用。子模式可以有以下作用:
1. 将多选一的分支局部化。
例如,模式: cat(aract|erpillar|)匹配了 "cat","cataract" 或 "caterpillar" 之一,没有圆括号的话将匹配 "cataract","erpillar" 或空字符串。
2. 将子模式设定为捕获子模式(例如上面这个例子)。当整个模式匹配时,目标字符串中匹配了子模式的部分可以通过逆向引用进行调用。左圆括号从左到右计数(从 1 开始)以取得捕获子模式的数。
注意,子模式是可以嵌套的,例如,如果将字符串 "the red king" 来和模式 /the ((red|white) (king|queen))/进行匹配,捕获的子串为 "red king","red" 以及 "king",并被计为 1,2 和 3 ,可以通过“1”,“2”,“3”来分别引用它们,“1”包含了“2”和“3”,它们的序号是由左括号的顺序决定的。
在一些老的linux/unux工具里,子模式使用的圆括号需要用反斜线转义,向这种(subpattern),但现代的工具已经不需要了,本文中使用的例子都不进行转义。
非捕获子模式(non-capturing subpatterns)
用一对括号同时完成上面提到的子模式的两个功能有时会出现一些问题,例如,由于逆向引用的数目是有限的(通常最大不超过9),而且经常会遇到无需捕获的子模式定义。这时,可以在开始的括号后加上问号和冒号来表示这个子模式无需捕获,就向下面这样:((?:red|white) (king|queen))。
如果将“the white queen”作为模式匹配的目标字符串,则捕获的字串有“white queen”和“queen”,分别作为“1”和“2”,white虽然符合子模式“(?:red|white)”,但并不被捕获。
我们前面已经介绍过用括号与问号表示模式修正符的方法,为方便起见,如果需要在非捕获子模式中插入模式修正符,可以把它直接放在问号和冒号之间,例如,下面两个模式是等效的。
/(?i:saturday|sunday)/和/(?:(?i)saturday|sunday)/。
逆向引用(Back references)
前面介绍反斜线作用时,已经提到它的一个作用就是表示逆向引用,当字符类之外的反斜线后跟一个大于0的十进制数时,它很有可能是一个逆向引用。它的含义正如它的名称如言,它表示对它出现之前已经捕获的子模式的引用。这个数字代表了它引用的左括号在模式中出现的次序,我们在介绍子模式时已经看到过逆向引用的一个例子,那里的过“1”,“2”,“3”分别表示所捕获的第一,第二,和第三个小括号定义的子模式的内容。
值得注意的是,当反斜线后的数字小于10时,可以确定此为一个逆向引用,这样,这个逆向引用就可以出现在之前有相应数目的左圆括号被捕获前而不会出现混淆,只有整个模式能提供那么多的捕获子模式,就不会报错。说起来似乎很混乱,还是让我们来看下面这个例子。把介绍子模子时举的例子拿来修改一下,前面讲过字符串 "the red king" 来和模式 /the ((red|white) (king|queen))/匹配,捕获的子串为 "red king","red" 以及 "king",并被计为 1,2 和 3 ,现在把字符串,修改为" king,the red king",模式改为/3,the ((red|white) (king|queen))/,这个模式应该也是可以匹配的。不过,并非所有的正则表达式工具都支持这种用法,安全的做法是在相应序号的左括号之后使用与之相关的逆向引用。
需要注意的另一点是逆向引用的值是在目标字符串中实际捕获的符合子模式的字符串片段而非该子模式本本身。例如/ (sens|respons)e and 1ibility/会匹配“sense and sensibility” 和 “response and responsibility”,但不会是 "sense and responsibility"。当被逆向引用的子模式后面有量词从而被重复匹配了多次,逆向引用的值会以最后一次匹配的值为准。例如/([abc]){3}/匹配字符串“abc”时,逆向引用“1”的值将是最后一次匹配的结果“c”。
相信这里大家对Linux正则表达式的子模式(subpatterns)与逆向引用(Back references)已经有所了解了,后面还有更精彩内容。