正则表达式入门教程
2010-07-13 09:44:52 阿炯

短时间内让你明白正则表达式是什么,并对它有一些基本的了解,可以在自己的程序或网页里使用它。一旦入门后又可以从网上找到更多更详细的资料来继续学习。别被下面那些复杂的表达式吓倒,只要一步一步来,你会发现正则表达式其实并不像你想像中的那么困难。当然,如果你看完了这篇教程之后发现自己明白了很多,却又几乎什么都记不得,那也是很正常的:能记住80%以上的可能性为零。这里只是让你明白基本道理,以后你还需要多练习,多查资料,才能熟练掌握正则表达式。

正则表达式是用于进行文本匹配的工具,所以本文里多次提到了在字符串里搜索/查找,这种说法的意思是在给定的字符串中,查找与给定的正则表达式相匹配的部分。有可能字符串里有不止一个部分满足给定的正则表达式,这时每一个这样的部分被称为一个匹配。匹配在本文里可能会有三种意思:一种是形容词性的,比如说一个字符串匹配一个表达式;一种是动词性的,比如说在字符串里匹配正则表达式;还有一种是名字性的,就是刚刚说到的“字符串中满足给定的正则表达式的一部分”。

很可能你使用过Windows/Dos下用于文件查找的通配符,也就是*和?。如果你想查找某个目录下的所有的Word文档的话,你会搜索*.doc。在这里,*会被解释成任意的字符串。和通配符类似,正则表达式也是用来进行文本匹配的工具,只不过比通配符更能精确地描述你的需求--当然,代价就是更复杂。比如你可以编写一个正则表达式来查找所有以0开头,后面跟着2-3个数字,然后是一个连字号“-”,最后是7或8位数字的字符串(像010-12345678或0376-7654321)。

在编写处理字符串的程序或网页时,经常会有查找符合某些复杂规则的字符串的需要。正则表达式就是用于描述这些规则的工具。换句话说,正则表达式就是记录文本规则的代码。例如,\d+就是一个简洁的代码,代表着规则1位或更多位数字,2008就符合这个规则,而A3则不符合(它包含了不是数字的字符)。学习正则表达式的最好方法是从例子开始,理解例子之后再自己对例子进行修改,实验。下面给出了不少简单的例子,并对它们作了详细的说明。

假设在一篇英文小说里查找hi,你可以使用正则正则表达式hi。
这是最简单的正则表达式了,它可以精确匹配这样的字符串:由两个字符组成,前一个字符是h,后一个是i。通常,处理正则表达式的工具会提供一个忽略大小写的选项,如果选中了这个选项,它可以匹配hi,HI,Hi,hI。
不幸的是,很多单词里包含hi这两个连续的字符,比如him,history,high等等。用hi来查找的话,这里边的hi也会被找出来。如果要精确地查找hi这个单词的话,我们应该使用\bhi\b。
\b是正则表达式规定的一个特殊代码,代表着单词的开头或结尾。虽然通常英文的单词是由空格或标点符号或换行为分隔的,但是\b并不代表这些单词分隔符中的任何一个,只代表一个位置。
假如你要找的是hi后面不远处跟着一个Lucy,你应该用\bhi\b.*\bLucy\b。
这里,.是另一个特殊代码,代表除了换行符以外的任意字符。*同样是特殊的代码,不过它代表的不是字符,也不是位置,而是数量--它指定*前边的内容可以重复任意次以使整个表达式得到匹配。因此,.*连在一起就意味着任意数量的不包含换行的字符。现在\bhi\b.*\bLucy\b的意思就很明显了:先是一个单词hi,然后是任意个任意字符(但不能是换行),最后是Lucy这个单词。
如果同时使用其它的一些特殊代码,我们就能构造出功能更强大的正则表达式。比如下面这个例子:
0\d\d-\d\d\d\d\d\d\d\d代表着这样的字符串:以0开头,然后是两个数字,然后是一个连字号“-”,最后是8个数字(也就是中国的电话号码,当然,这个例子只能匹配区号为3位的情形,想同时匹配区号为4位的话,请在教程的下面寻找答案)。
这里的\d是一个新的特殊代码,代表任意的数字(0,或1,或2,或。。。)。-不是特殊代码,只代表它本身--连字号。
为了避免那么多烦人的重复,我们也可以这样写这个表达式:0\d{2}-\d{8}
这里\d后面的{2}({8})指定的是前面\d必须连续重复出现2次(8次)。

测试正则表达式
如果你不觉得正则表达式很难读写的话,要么你是一个天才,要么,你不是地球人。正则表达式的语法很令人头疼,即使对经常使用它的人来说也是如此。由于难于读写,容易出错,所以很有必要创建一种工具来测试正则表达式。
由于在不同的环境下正则表达式的一些细节是不相同的,本教程介绍的是Microsoft .net下正则表达式的行为,所以,我向你介绍一个.net下的工具The Regulator。首先你确保已经安装了.net Framework1.1,然后下载The Regulator,下载完后打开压缩包,运行setup.exe安装。

特殊代码
现在你已经知道几个具有特殊意义的代码了,如\b,.,*,还有\d.事实上还有更多的特殊代码,比如\s代表任意的空白符,包括空格,制表符(Tab),换行符。\w代表着字母或数字。

下面来试试更多的例子:
\ba\w*\b匹配以字母a开头的单词-先是某个单词开始处(\b),然后是字母a,然后是任意数量的字母或数字(\w*),最后是单词结束处(\b)。
\d+匹配1个或更多连续的数字。这里的+是和*类似的特殊代码,不同的是*代表重复任意次(可能是0次),而+则代表重复1次或更多次。
\b\w{6}\b匹配刚好6个字母/数字的单词。

表1.常用的特殊代码代码/语法    说明
.    匹配除换行符以外的任意字符
\w    匹配字母或数字
\s    匹配任意的空白符
\d    匹配数字
\b    匹配单词的开始或结束
^    匹配字符串的开始
$    匹配字符串的结束

特殊代码^以及$和\b有点类似,都匹配一个位置。^匹配你要用来查找的字符串的开头,$匹配结尾。这两个代码在验证输入的内容时非常有用,比如一个网站如果要求你填写的QQ号必须为5位到12位数字时,可以使用:^\d{5,12}$。
这里的{5,12}和前面介绍过的{2}是类似的,只不过{2}代表只能不多不少重复2次,{5,12}则是必须重复最少5次,最多12次,否则都不匹配。
因为使用了^和$,所以输入的整个字符串都要用来和\d{5,12}来匹配,也就是说整个输入必须是5到12个数字,因此如果输入的QQ号能匹配这个正则表达式的话,那就符合要求了。
和忽略大小写的选项类似,有些正则表达式处理工具还有一个处理多行的选项。如果选中了这个选项,^和$的意义就变成了匹配行的开始处和结束处。

字符转义
如果你想查找特殊代码本身的话,比如你查找.,或者*,就出现了问题:你没法指定它们,因为它们会被解释成其它的意思。这时你就必须使用\来取消这些字符的特殊意义。因此,你应该使用\.和\*。当然,要查找\本身,你也得用\\.
例如:www\.unibetter\.com匹配www.unibetter.com,c:\\windows匹配c:\windows,2\^8匹配2^8(通常这是2的8次方的书写方式)。

重复
你已经看过了前面的*,+,{2},{5,12}这几个代表重复的方式了。下面是正则表达式中所有指定重复的方式:
表2.常用的量词代码/语法    说明
*    重复零次或更多次
+    重复一次或更多次
?    重复零次或一次
{n}    重复n次
{n,}    重复n次或更多次
{n,m}    重复n到m次

下面是一些使用重复的例子:
Windows\d+匹配Windows后面跟1个或更多数字
13\d{9}匹配以13后面跟9个数字(中国的手机号)
^\w+匹配一行的第一个单词(或整个字符串的第一个单词,具体代表哪个意思得看选项设置)

字符类
要想查找数字,字母或数字,空白是很简单的,因为已经有了对应这些字符集的特殊代码,但是如果你想匹配没有预定义特殊代码的字符集比如元音字母(a,e,i,o,u),怎么办?
很简单,你只需要在中括号里列出它们就行了,像[aeiou]就匹配任何一个元音字母,[.?!]匹配标点符号(.或?或!)(英文语句通常只以这三个标点结束)。要注意的是,在中括号中,特殊代码不会被解释成其它意义,所以我们不需要写成[\.\?!](事实上这样写会出错,因为出现了两次\)。
我们也可以轻松地指定一个字符范围,像[0-9]代表的含意与\d就是完全一致的:一位数字,同理[a-z0-9A-Z]也完全等同于\w。

下面是一个更复杂的表达式:\(?0\d{2}[) -]?\d{8}。
这个表达式可以匹配几种格式的电话号码,像(010)88886666,或022-22334455,或02912345678等。我们对它进行一些分析吧:首先是一个转义字符\(,它能出现0次或1次(?),然后是一个0,后面跟着2个数字({2}),然后是)或-或空格中的一个,它出现1次或不出现(?),最后是8个数字(\d{8})。不幸的是,它也能匹配010)12345678或(022-87654321这样的“不正确”的格式。要解决这个问题,请在本教程的下面查找答案。

反义
有时需要查找不属于某个简单定义的字符类的字符。比如想查找除了数字以外,其它任意字符都行的情况,这时需要用到反义:
表3.常用的反义代码代码/语法    说明
\W    匹配任意不是字母和数字的字符
\S    匹配任意不是空白符的字符
\D    匹配任意非数字的字符
\B    匹配不是单词开头或结束的位置
[^x]    匹配除了x以外的任意字符
[^aeiou]    匹配除了aeiou这几个字母以外的任意字符

例子:\S+代表不包含空白符的字符串。
]+>代表用尖括号括起来的以a开头的字符串。

替换
好了,现在终于到了解决3位或4位区号问题的时间了。正则表达式里的替换指的是有几种规则,如果满足其中任意一种规则都应该当成匹配,具体方法是用|把不同的规则分隔开。听不明白?没关系,看例子:
0\d{2}-\d{8}|0\d{3}-\d{7}这个表达式能匹配两种以连字号分隔的电话号码:一种是三位区号,8位本地号(如010-12345678),一种是4位区号,7位本地号(0376-2233445)。
\(0\d{2}\)[- ]?\d{8}|0\d{2}[- ]?\d{8}这个表达式匹配3位区号的电话号码,其中区号可以用小括号括起来,也可以不用,区号与本地号间可以用连字号或空格间隔,也可以没有间隔。你可以试试用替换|把这个表达式扩展成也支持4位区号的。
\d{5}-\d{4}|\d{5}这个表达式用于匹配美国的邮政编码。美国邮编的规则是5位数字,或者用连字号间隔的9位数字。之所以要给出这个例子是因为它能说明一个问题:使用替换时,顺序是很重要的。如果你把它改成\d{5}|\d{5}-\d{4}的话,那么就只会匹配5位的邮编(以及9位邮编的前5位)。原因是匹配替换时,将会从左到右地测试每个条件,如果满足了某个条件的话,就不会去管其它的替换条件了。
Windows98|Windows2000|WindosXP这个例子是为了告诉你替换不仅仅能用于两种规则,也能用于更多种规则。

分组
我们已经提到了怎么重复单个字符;但如果想要重复一个字符串又该怎么办?你可以用小括号来指定子表达式(也叫做分组),然后你就可以指定这个子表达式的重复次数了,你也可以对子表达式进行其它一些操作(教程后面会有介绍)。
(\d{1,3}\.){3}\d{1,3}是一个简单的IP地址匹配表达式。要理解这个表达式,请按下列顺序分析它:\d{1,3}代表1到3位的数字,(\d{1,3}\.}{3}代表三位数字加上一个英文句号(这个整体也就是这个分组)重复3次,最后再加上一个一到三位的数字(\d{1,3})。
不幸的是,它也将匹配256.300.888.999这种不可能存在的IP地址(IP地址中每个数字都不能大于255)。如果能使用算术比较的话,或许能简单地解决这个问题,但是正则表达式中并不提供关于数学的任何功能,所以只能使用冗长的分组,选择,字符类来描述一个正确的IP地址:((2[0-4]\d|25[0-5]|[01]?\d\d?)\.){3}(2[0-4]\d|25[0-5]|[01]?\d\d?)。
理解这个表达式的关键是理解2[0-4]\d|25[0-5]|[01]?\d\d?,这里我就不细说了,你自己应该能分析得出来它的意义。

后向引用
使用小括号指定一个子表达式后,匹配这个子表达式的文本可以在表达式或其它程序中作进一步的处理。默认情况下,每个分组会自动拥有一个组号,规则是:以分组的左括号为标志,从左向右,第一个分组的组号为1,第二个为2,以此类推。
后向引用用于重复搜索前面某个分组匹配的文本。例如,\1代表分组1匹配的文本。难以理解?请看示例:
\b(\w+)\b\s+\1\b可以用来匹配重复的单词,像go go,kitty kitty。首先是一个单词,也就是单词开始处和结束处之间的多于一个的字母或数字(\b(\w+)\b),然后是1个或几个空白符(\s+,最后是前面匹配的那个单词(\1)。
你也可以自己指定子表达式的组号或组名。要指定一个子表达式的组名,请使用这样的语法:(?\w+),这样就把\w+的组名指定为Word了。要反向引用这个分组捕获的内容,你可以使用\k,所以上一个例子也可以写成这样:\b(?\w+)\b\s*\k\b。
使用小括号的时候,还有很多特定用途的语法。下面列出了最常用的一些:

表4.分组语法捕获
(exp)    匹配exp,并捕获文本到自动命名的组里
(?exp)    匹配exp,并捕获文本到名称为name的组里
(?:exp)    匹配exp,不捕获匹配的文本
位置指定
(?=exp)    匹配exp前面的位置
(?<=exp)    匹配exp后面的位置
(?!exp)    匹配后面跟的不是exp的位置
(?
注释
(?#comment)    这种类型的组不对正则表达式的处理产生任何影响,只是为了提供让人阅读注释
我们已经讨论了前两种语法。第三个(?:exp)不会改变正则表达式的处理方式,只是这样的组匹配的内容不会像前两种那样被捕获到某个组里面。

位置指定
接下来的四个用于查找在某些内容(但并不包括这些内容)之前或之后的东西,也就是说它们用于指定一个位置,就像\b,^,$那样,因此它们也被称为零宽断言。最好还是拿例子来说明吧:
(?=exp)也叫零宽先行断言,它匹配文本中的某些位置,这些位置的后面能匹配给定的后缀exp。比如\b\w+(?=ing\b),匹配以ing结尾的单词的前面部分(除了ing以外的部分),如果在查找I'm singing while you're dancing.时,它会匹配sing和danc。
(?<=exp)也叫零宽后行断言,它匹配文本中的某些位置,这些位置的前面能给定的前缀匹配exp。比如(?<=\bre)\w+\b会匹配以re开头的单词的后半部分(除了re以外的部分),例如在查找reading a book时,它匹配ading。
假如你想要给一个很长的数字中每三位间加一个逗号(当然是从右边加起了),你可以这样查找需要在前面和里面添加逗号的部分:((?<=\d)\d{3})*\b。请仔细分析这个表达式,它可能不像你第一眼看出来的那么简单。
下面这个例子同时使用了前缀和后缀:(?<=\s)\d+(?=\s)匹配以空白符间隔的数字(再次强调,不包括这些空白符)。

负向位置指定
前面我们提到过怎么查找不是某个字符或不在某个字符类里的字符的方法(反义)。但是如果我们只是想要确保某个字符没有出现,但并不想去匹配它时怎么办?例如,如果我们想查找这样的单词--它里面出现了字母q,但是q后面跟的不是字母u,我们可以尝试这样:
\b\w*q[^u]\w*\b匹配包含后面不是字母u的字母q的单词。但是如果多做测试(或者你思维足够敏锐,直接就观察出来了),你会发现,如果q出现在单词的结尾的话,像Iraq,Benq,这个表达式就会出错。这是因为[^u]总是匹配一个字符,所以如果q是单词的最后一个字符的话,后面的[^u]将会匹配q后面的单词分隔符(可能是空格,或者是句号或其它的什么),后面的\w+\b将会匹配下一个单词,于是\b\w*q[^u]\w*\b就能匹配整个Iraq fighting。负向位置指定能解决这样的问题,因为它只匹配一个位置,并不消费任何字符。现在,我们可以这样来解决这个问题:\b\w*q(?!u)\w*\b。
零宽负向先行断言(?!exp),只会匹配后缀exp不存在的位置。\d{3}(?!\d)匹配三位数字,而且这三位数字的后面不能是数字。
同理,我们可以用(?一个更复杂的例子:(?<=).*(?=)匹配不包含属性的简单HTML标签内里的内容。()指定了这样的前缀:被尖括号括起来的单词(比如可能是),然后是.*(任意的字符串),最后是一个后缀(?=)。注意后缀里的\/,它用到了前面提过的字符转义;\1则是一个反向引用,引用的正是捕获的第一组,前面的(\w+)匹配的内容,这样如果前缀实际上是的话,后缀就是了。整个表达式匹配的是和之间的内容(再次提醒,不包括前缀和后缀本身)。

注释
小括号的另一种用途是能过语法(?#comment)来包含注释。要包含注释的话,最好是启用“忽略模式里的空白符”选项,这样在编写表达式时能任意的添加空格,Tab,换行,而实际使用时这些都将被忽略。启用这个选项后,在#后面到这一行结束的所有文本都将被当成注释忽略掉。例如,我们可以把上一个表达式写成这样:
(?<=    # 查找前缀,但不包含它
# 查找尖括号括起来的字母或数字(标签)
)       # 前缀结束
.*      # 匹配任意文本
(?=     # 查找后缀,但不包含它
  # 查找尖括号括起来的内容:前面是一个"/",后面是先前捕获的标签
)       # 后缀结束

贪婪与懒惰
当正则表达式中包含能接受重复的量词(指定数量的代码,例如*,{5,12}等)时,通常的行为是匹配尽可能多的字符。考虑这个表达式:a.*b,它将会匹配最长的以a开始,以b结束的字符串。如果用它来搜索aabab的话,它会匹配整个字符串aabab。这被称为贪婪匹配。
有时,我们更需要懒惰匹配,也就是匹配尽可能少的字符。前面给出的量词都可以被转化为懒惰匹配模式,只要在它后面加上一个问号?。这样.*?就意味着匹配任意数量的重复,但是在能使整个匹配成功的前提下使用最少的重复。现在看看懒惰版的例子吧:
a.*?b匹配最短的,以a开始,以b结束的字符串。如果把它应用于aabab的话,它会匹配aab和ab。

表5.懒惰量词*?    重复任意次,但尽可能少重复
+?    重复1次或更多次,但尽可能少重复
??    重复0次或1次,但尽可能少重复
{n,m}?    重复n到m次,但尽可能少重复
{n,}?    重复n次以上,但尽可能少重复

还有些什么东西没提到
我已经描述了构造正则表达式的大量元素,还有一些我没有提到的东西。下面是未提到的元素的列表,包含语法和简单的说明。你可以在网上找到更详细的参考资料来学习它们--当你需要用到它们的时候。如果你安装了MSDN Library,你也可以在里面找到关于.net下正则表达式详细的文档。
表6.尚未讨论的语法\a    报警字符(打印它的效果是电脑嘀一声)
\b    通常是单词分界位置,但如果在字符类里使用代表退格
\t    制表符,Tab
\r    回车
\v    竖向制表符
\f    换页符
\n    换行符
\e    Escape
\0nn    ASCII代码中八进制代码为nn的字符
\xnn    ASCII代码中十六进制代码为nn的字符
\unnnn    Unicode代码中十六进制代码为nnnn的字符
\cN    ASCII控制字符。比如\cC代表Ctrl+C
\A    字符串开头(类似^,但不受处理多行选项的影响)
\Z    字符串结尾或行尾(不受处理多行选项的影响)
\z    字符串结尾(类似$,但不受处理多行选项的影响)
\G    当前搜索的开头
\p{name}    Unicode中命名为name的字符类,例如\p{IsGreek}
(?>exp)    贪婪子表达式
(?-exp)    平衡组
(?-exp)    平衡组
(?im-nsx:exp)    在子表达式exp中改变处理选项
(?im-nsx)    为表达式后面的部分改变处理选项
(?(exp)yes|no)    把exp当作零宽正向先行断言,如果在这个位置能匹配,使用yes作为此组的表达式;否则使用no
(?(exp)yes)    同上,只是使用空表达式作为no
(?(name)yes|no)    如果命名为name的组捕获到了内容,使用yes作为表达式;否则使用no
(?(name)yes)    同上,只是使用空表达式作为no


不少需要使用到正则的服务软件一般都采用PCRE(如httpd、nginx、haproxy、proxysql)库,它和Perl正则几乎完全一致,作为运维人员有必要学习掌握这些最基础的正则表达。

元字符

.:匹配任意单个字符,但不能匹配换行符\n
*:匹配前面那个字符0或多次
?:匹配前面那个字符0或一次
+:匹配前面那个字符1次以上
{M,N}:匹配前面那个字符至少M,最多N次
{M,}:匹配前面那个字符至少M次,最多无限制
{,N}:匹配前面那个字符最多N次(最少当然是0次)。注意,perl正则不支持这种方式
{M}:匹配前面那个字符正好M次
锚定:锚定的意思是匹配位置,而非匹配字符实体
    ^:匹配行首位置,注意匹配的是位置,不是字符
    $:匹配行尾位置,注意匹配的是位置,不是字符

特殊且常用的的组合正则表达式:
^$:它表示匹配空行
.*:匹配任意长度的任意字符,但不能匹配换行符。真正的匹配任意长度的任意字符,见下面。

需要解释清楚的是这些量词(也就是上面匹配的次数元字符)的特殊性:当使用了匹配多次的量词时(如匹配3-5次的{3,5}),且量词前面的字符有多种可能性(如中括号序列[abc]),那么量词的次数可以作用于任一字符。有些不好理解,但看示例就知道了:
[abc]{3,5} # 表示abc任意字符都可以出现,比如全是a,或者ab同时出现,但总的出现次数为3-5次
.* # 表示任一字符(除换行符),可以任意出现任意次数,它不表示a之后就必须全是a
.+ # 表示任一字符(除换行符),可以任意出现至少一次,它不表示a之后就必须全是a

另外,'.'无法匹配换行符。可能不太理解为什么需要匹配换行符,它主要用在:
1.多行模式。例如sed的多行模式下,要跨行匹配需要手动指定"\n",如/^a.*\nb.*/
2.明确指定了行分隔符为非"\n"的情况。例如awk可以使用RS变量指定输入行分隔符

中括号

中括号表示的是匹配任意一个,一般它和字符集的排序规则有关,不同工具采取的排序规则可能也不一样。
[abcd...]:匹配中括号内的任意一个字符
[^abcd...]:拒绝匹配中括号内的任意字符
[a-z]:匹配字母a到z
[A-Z]:匹配字母A到Z
[0-9]:匹配0-9,也就是匹配数字

关于字母的排序:
perl中,A-Z排在a的前面,所以[A-z]表示所有大小写字母
grep中,A-Z排在z的后面,所以[a-Z]表示所有大小写字母
还有些工具中,大小写的排序规则是aAbBcC...zZ,所以[a-C]表示aAbBcC共6个字母

字符类

是专门命名的中括号序列;除了字符类,还有等价类、排序类,但基本用不上,只用字符类。
[:alpha:]:匹配字母,等价于[a-zA-Z]
[:digit:]:匹配数字,等价于[0-9]
[:xdigit:]:匹配十六进制数,等价于[0-9a-fA-F]
[:upper:]:匹配大写字母,等价于[A-Z]
[:lower:]:匹配小写字母,等价于[a-z]
[:alnum:]:匹配数字或字母,等价于[0-9a-zA-Z]
[:blank:]:匹配空白,包括空格和制表符
[:space:]:匹配空格,包括空格、制表符、换行符、回车符等各种类型的空白
[:punct:]:匹配标点符号。包括:! ' " ` # $ % & ( ) * + , . - _ / : ; < = > ? @ [ \ ] ^ { | } ~
[:graph:]:绘图类。包括:大小写字母、数字和标点符号。等价于[:alnum:]+[:punct:]
[:print:]:打印字符类。包括:大小写字母、数字、标点符号和空格。等价于[:alnum:]+[:punct:]+space
[:cntrl:]:控制字符类。在ASCII中,这些字符的八进制代码从000到037,还包括177(DEL)

需要注意的是,通常字符类在真正使用过程中,会再加上一个中括号,例如[[:alpha:]]。之所以如此,是因为这些字符类只是一种命名好的字符集合。例如[:lower:]对应的字符集合是a-z,而不是[a-z],所以要想让其表示这些命名字符类中的任一字符,需要再加上一层括号[[:lower:]],它才等价于[a-z]。可能会更有助于理解使用字符类的时候为什么要加两个中括号的例子是[^[:lower:]],它表示不包含任何小写字母。

反斜线序列

不同的工具,同一工具不同的版本,支持的反斜线序列能力不同。以下列出了部分常见序列。以下所说的单词,一般来说只包含数字、字母和下划线,即[_0-9a-zA-Z]。以下几种反斜线序列,基本上所有工具都支持:
\b:匹配单词边界处的空字符
\B:匹配非单词边界处的空字符
\<:匹配单词开头处的空字符
\>:匹配单词结尾处的空字符
\w:匹配单词构成部分,等价于[_[:alnum:]]
\W:匹配非单词构成部分,等价于[^_[:alnum:]]

以下几种,有些工具不支持,但perl都支持:
\s:匹配空白字符,等价于[[:space:]]
\S:匹配非空白字符,等价于[^[:space:]]
\d:匹配数字,等价于[0-9]
\D:匹配非数字,等价于[^0-9]

由于元字符.默认无法匹配换行符,所以需要匹配换行符的时候,可以使用特殊组合[\d\D]来替换.,换句话说,如果想匹配任意长度的任意字符,可以换成[\d\D]*,当然,前提是必须支持\d和\D两个反斜线序列。

分组捕获和反向引用

基础正则中,使用括号可以对匹配内容进行分组并暂时保存,分组后会有分组编号,可以使用反斜线加编号\N的方式反向引用这些分组。

分组编号的方式是从左向右计算括号数,无论如何嵌套,第一个左括号对应的分组一定是编号1,用\1来引用,第二个左括号对应的分组一定是编号2,用\2来引用,依此类推。

例如grep的分组捕获:匹配两个连续相同的字母:
echo "abcddefg" | grep -E "(.)\1"

可以认为分组就是变量赋值的过程。例如,上面示例的匹配过程如下:
1.匹配第一个字母a,放进分组,即赋值给变量(假设变量名为$1),即$1="a",再继续执行正则表达式匹配过程的反向引用,它引用的是$1,于是表示第一个字母a后面还要是字母a,于是匹配失败。
2.匹配第二个字母b,放进分组,即$1="b",再匹配后一个字母,于是匹配失败。
3.字母c同样如此。
4.匹配字母d,放进分组,即$1="d",再匹配后一个字母,发现匹配成功,于是$1被保存下来。
5.已经匹配成功,于是结束。

对于只使用基础正则的工具来说,一般都只能引用\1到\9共9个反向引用,最多自己额外提供一个所有表示匹配内容的反向引用(例如sed提供的&)。对于超出10个的分组,使用基础正则的工具一般来说是无能为力的。

再者,基础正则仅仅只是将分组匹配到的内容捕获,在正则操作结束后就丢失。但对于一门完整编程语言来说,这远远不够,几乎所有编程语言(如perl/ruby/python等)都会将正则的分组匹配内容保存为变量,使得可以在正则结束之后再次引用甚至修改它们。例如上面例子中分组捕获的字母d,如果换成perl,即使在这个匹配过程结束后,还是可以去引用这段分组。

二选一

pattern1 | pattern2:匹配竖线左边,或者匹配竖线右边都算匹配成功。

关于二选一的结构,几点需要说明:
1.因为竖线元字符的优先级很低,所以ab|cd匹配的是"ab"或"cd",而不是abd或acd。
2.成功匹配了左边,就不会再去对右边进行匹配。
3.反向引用失败问题:竖线将两边的分组隔开,右边的永远无法反向引用左边的分组。

在二选一结构中,两个反向引用问题的典型例子:
例如a(.)|b\1将无法匹配"ba",因为评估了左边就不会评估右边。

例如([ac])e\1|b([xyz])\2t的左边能匹配aea或cec,但不能匹配cea或aec,右边能匹配bxxt或byyt或bzzt。但如果将\2换成\1,即([ac])e\1|b([xyz])\1t,将无法匹配b[xyz]at或b[xyz]ct,因为第一个分组括号在左边,无法参与右边的正则评估。


常用正则表达式
写在前面: 正则表达式用于字符串处理、表单验证等场合,实用高效。但用到时总是不太把握,以致往往要查证一番。现将一些常用的表达式收集于此,以备不时之需。





注: 以下表达式均在RegexBuddy测试修正过,若有错,还望指出。另外注意,在不同的语言环境下,对正则的支持都可能不一样,实际应用时注意不同之处

匹配中文字符的正则表达式: [\u4e00-\u9fa5]
评注:匹配中文还真是个头疼的事,有了这个表达式就好办了

匹配双字节字符(包括汉字在内):[^\x00-\xff]
评注:可以用来计算字符串的长度(一个双字节字符长度计2,ASCII字符计1)

匹配空白行的正则表达式:\n\s*\r
评注:可以用来删除空白行

匹配HTML标记的正则表达式:]*>.*?|
评注:网上流传的版本太糟糕,上面这个也仅仅能匹配部分,对于复杂的嵌套标记依旧无能为力

匹配首尾空白字符的正则表达式:^\s*|\s*$
评注:可以用来删除行首行尾的空白字符(包括空格、制表符、换页符等等),非常有用的表达式

匹配Email地址的正则表达式:\w+([-+.]\w+)*@\w+([-.]\w+)*\.\w+([-.]\w+)*
评注:表单验证时很实用

匹配网址URL的正则表达式:[a-zA-z]+://[^\s]*
评注:网上流传的版本功能很有限,上面这个基本可以满足需求

匹配帐号是否合法(字母开头,允许5-16字节,允许字母数字下划线):^[a-zA-Z][a-zA-Z0-9_]{4,15}$
评注:表单验证时很实用

匹配国内电话号码:\d{3}-\d{8}|\d{4}-\d{7}
评注:匹配形式如 0511-4405222 或 021-87888822

匹配腾讯QQ号:[1-9][0-9]{4,}
评注:腾讯QQ号从10000开始

匹配中国邮政编码:[1-9]\d{5}(?!\d)
评注:中国邮政编码为6位数字

匹配身份证:\d{15}|\d{18}
评注:中国的身份证为15位或18位

匹配ip地址:\d+\.\d+\.\d+\.\d+
评注:提取ip地址时有用

匹配特定数字:
^[1-9]\d*$    //匹配正整数
^-[1-9]\d*$   //匹配负整数
^-?[1-9]\d*$   //匹配整数
^[1-9]\d*|0$  //匹配非负整数(正整数 + 0)
^-[1-9]\d*|0$   //匹配非正整数(负整数 + 0)
^[1-9]\d*\.\d*|0\.\d*[1-9]\d*$   //匹配正浮点数
^-([1-9]\d*\.\d*|0\.\d*[1-9]\d*)$  //匹配负浮点数
^-?([1-9]\d*\.\d*|0\.\d*[1-9]\d*|0?\.0+|0)$  //匹配浮点数
^[1-9]\d*\.\d*|0\.\d*[1-9]\d*|0?\.0+|0$   //匹配非负浮点数(正浮点数 + 0)
^(-([1-9]\d*\.\d*|0\.\d*[1-9]\d*))|0?\.0+|0$  //匹配非正浮点数(负浮点数 + 0)
评注:处理大量数据时有用,具体应用时注意修正

匹配特定字符串:
^[A-Za-z]+$  //匹配由26个英文字母组成的字符串
^[A-Z]+$  //匹配由26个英文字母的大写组成的字符串
^[a-z]+$  //匹配由26个英文字母的小写组成的字符串
^[A-Za-z0-9]+$  //匹配由数字和26个英文字母组成的字符串
^\w+$  //匹配由数字、26个英文字母或者下划线组成的字符串


校验字符的表达式

汉字:^[\u4e00-\u9fa5]{0,}$

中文、英文、数字包括下划线:^[\u4E00-\u9FA5A-Za-z0-9_]+$

中文、英文、数字但不包括下划线等符号:^[\u4E00-\u9FA5A-Za-z0-9]+$ 或 ^[\u4E00-\u9FA5A-Za-z0-9]{2,20}$

禁止输入含有~的字符[^~\x22]+

.*匹配除\n以外的任何字符。

/[\u4E00-\u9FA5]/汉字

/[\uFF00-\uFFFF]/全角符号

/[\u0000-\u00FF]/半角符号

双字节字符:[^\x00-\xff](包括汉字在内,可以用来计算字符串的长度(一个双字节字符长度计2,ASCII字符计1))。


评注:最基本也是最常用的一些表达式








该文章最后由 阿炯 于 2023-11-24 15:46:34 更新,目前是第 2 版。