正则表达式用于对字符串的查找和替换,有着强大的功能。本文记录了正则表达式的一般规则,以方便查阅。

大部分语言都支持正则表达式,但是不同的语言之间又有那么一点的不同。本文以php支持的正则表达式为例(当然大部分在其它语言也是可以使用的),你在学习的过程中,可以通过本站的正则表达式测试工具进行测试,本工具是基于php制作的。如果你想学习正则表达式,那么请你在阅读本文的时候一定要多动手测试,多思考。

有关本文的一些约定

为了书写方便以及结合php相关函数,本文中使用patten:代表正则表达式,以subject:代表源字符串,以matches:代表查找结果(匹配结果)。

一个简单的正则表达式

在正式开始之前,我想让你知道,其实正则表达式并没有你想像的那么难,只是一些规则而已。我们来看一下如何用正则表达式从源字符中查找一个目标字符串:从domety中查找me

patten:me
subject:domety

你可以用正则表达测试工具测试一下,看一下结果。简单?就这么简单!我们用正则表达的思想来解释一下这个正则表达式(me)的意思:字母m之后紧跟着一个字母e。而源字符串domety中正好也存在这样的字符串,所以匹配的结果就是me。

特殊字符

如果像上面例子中只是简单的查找一个字符串,也就没必要使用正则表达式了。正则表达式之所以强大,还是因为这些特殊字符的存在。

开始和结束(^和$)

字符^代表字符串的开始位置,$代表字符串的结束位置。请注意这两个字符其实并不匹配任何字符,只是一个位置。我们现在把上面的例子改成patten:^me,再测试一下你会发现匹配结果为空。因为^me的意思是:查找以字母m开头,紧接着一个字母e的字符串。如果你把正则表达式改为patten:^do,那么匹配结果就为do。

对于$的理解也是一样,比如do$的意思就是:字符串以字母o结束,并且之前是一个字母d。如果你用do$去匹配domety,结果肯定为空。我们再来看一下这样一个正则表达式:^domety$,它的意思是:以d开头,紧接着一个o,然后是一个m然后是一个e然后是一个t,最后以一个y结束。那么这样一个正则表达式就只匹配domety字符串,如果你想让它去试图匹配domety.com,结果将为空。

字符类([]和[^])

[]代表一个字符类,它之间可以包含无数个字符,但它只匹配其中的一个字符。比如[abc]只匹配a或者b或者c。我们举个例子:[Dd]dbug,它的意思是查找这样一个字符串:一个大写或小写的字母d,紧接着一个字母d然后是一个b然后是一个u然后是一个g。那么它即匹配ddbug又匹配Ddbug。

在字符类中,还支持使用-来代表一个范围,[a-z]匹配所有的小写字母,[0-9]测匹配数字。比如我们想查找<h1>到<h6>的标签

patten:<h[1-6]>
subject:<h1>title1></h1><h2>title2</h2><h6>title6</h6>

那么匹配结果就是h1,h2和h6,你可以亲自测试一下。

这里有一个问题:既然-在字符类代表了范围,那么如果我想在字符类中匹配-怎么呢?简单,只需要把-放在第一个或最后一个位置即可,比如[-a-z]即匹配-又匹配小写字母。

那么[^]是什么意思呢?它匹配不在字符类中的任意一个字符,比如[^a-z]匹配所有非小写字母、[^0-9]匹配所有的非数字字符。

所有字符(.)

字符.可以匹配所有字符,但是在字符类[]中仍然只匹配.,请测试表达式a.ca[.]c的区别,可以用它们分别匹配abca.c做测试,看结果有何不同。

或表达式(|)

|字符的作用有点像字符类[],都可以匹配多个字符中的任意一个,不过|还可以匹配多个字符串中的任意一个。比如ddbug|domety,即匹配ddbug又匹配domety。再如,“第一”用英文写法可以是first也可以是1st,通过|我们可以很方便的找出first或者1st。我们可以这样写正则表达式:first|1st,也可以写成(fir|1)st。请测试:

patten:(fir|1)st
subject:1st is first

可选(?)

简单的说就是在?之前的字符或表达式可以出现0次或1次,可有可无。比如说表达式ab?c即匹配abc又匹配ac,表达式(ab)?c即匹配c又匹配abc

重复(+或*)

+之前的字符或表达式至少要出现一次,可以无限重复。而在*之前的字符或表达式至少出现0次,也可以无限重复。最常用的组合是.+(任意多个任意字符,但至少要有一个字符)和.*(任意多个任意字符,也可以没有任何字符)。比如a.+b匹配a和b之间含有任何字符串的字符串(acb、acdb等),但是不会匹配ab。而表达式a.*b则匹配ab,这就两者的唯一区别。

上面说的是无限次的重复,还有一种方法是指定重复的次数{min,max},比如{2}重复两次,{3,9}重复3到9次。表达式ab{3}c匹配abbbc

转意字符(\)

上面介绍了一些特殊字符,那么如果我们想匹配这些特殊字符应该怎么做呢?比如我想查找0.5+2*3=?这样的字符串,如何写正则表达式?当然不能直接用0.5+2*3=? 这样一个表达式的意思就成了:一个或多个数字0,随后是一个任意字符,然后是1个或多个数字5,然后接着0个或多个数字2,然后是一个数字3接着0个或多个=号。

这个时候我就需要使用车转意字符\,把这些特殊字符转换成普通的字符。上面的正则表达式就可以这样写:0\.5\+2\*3=\?这个表达式就可以匹配字符串0.5+2*3=?

转意字符\不仅可以把特殊字符转化为普通字符,还可以把普通字符转化为特殊字符。比如\d就把一个普通的字符d转化成了一个特殊的含意,它不再匹配字母d,而是匹配所有的数字,它等效于[0-9]。\b则表示单词的边界,这为我们匹配某个单词带来了很大的方便,比如刚开始的例子中表达式do可以匹配domety,但是如果我们想查找do这个单词呢?就可以把表达式改成\bdo\b,这样再测试一下,你会发现它不在匹配domety,而可以匹配just do it,因为在这个字符串中do是一个单词。

常用的还有,\s代表空白字符(包括空格、制表符tab、回车符、换行符等),\w代表字母或者数字([a-zA-Z0-9])。\D代表非数字字符,\W代表非字符和数字的字符,\S代表非空白字符。

模式

一、忽略大小写(i)

当我们查找的字符串不需要区分大小写的时候,就可以使用忽略大小写的模式i,该模式的表达式结构为:(?i:)。比如表达式(?i:domety)可以匹配dometyDOMETYDometyDOmety等。除了这样写以外,还可以使用语言自带的方式,以php的preg_match为例:preg_match(‘/domety/i’,'Domety’);

二、单行模式(s)

单行模式的原名叫”dot-matches-all”,意思是.匹配所有字符。也许你要问了:点本来不就是匹配所有字符的吗?基本上说是的,但是它不会去匹配换行符。表达式.+一次只能匹配一行,如果你的字符串有两行的话,它会匹配两次。而使用单行模式,.的意义真正的是匹配所有字符,包括换行符。

单行模式的表达式结构为:(?s:)。比如表达式(?s:.+)一次就可以匹配所有的字符串,不管你的字符串有多少行。

三、多行模式

多行模式的原名叫”enhanced line-anchor”,它可以使^匹配任意一行字符串开始的位置,使$匹配任意一行字符串结束的位置,而不是整个字符串的开始和结束位置。

四、原义字符

前面我们说过,如果想要匹配一些特殊字符的时候,需要先使用转义字符把这些字符转换成普通字符。不过,除了这一种方法之外,还有一种方法,就是把表达式写在\Q\E之间。表达式:\Q0.5+2*3=?\E 可以完美的匹配字符串0.5+2*3=?

括号和引用

在前面的例子中,你肯定看到了()的用法,它可以对表达式进行分组,但还有另一个作用,就是()中的匹配结果已经被默默的保存到了一些“变量”中。这些“变量”的格式为\num,比如\1,\2,\3等。我们称这些“变量”为“反向引用”,我还是习惯把它们叫作“变量”。变量\1的值等于表达式中第一个括弧匹配的结果,\2的值等于第二个括弧匹配的结果,以此类推。

比如有这样一个正则表达式:(\w+)\s(\w+)它的意思是匹配以空白字符隔开的两个字符串,如果用它去匹配”ddbug domety“,那么\1的值就是ddbug,而\2的值为domety。

如果我们在正则表达式中使用这些变量,就会有很神奇的效果。最常见的一个例子是查找两个连续重复的单词,表达式是这样的:(\b\w+\b)\s\1。这里我就不举例说明具体的使用方法了,你可以用它去匹配任意两个继续的单词,看一下效果。最后再注意这个表达式和(\b\w+\b)\s(\b\w+\b)的区别。变量中保存的是一个实际的字符串,而不是表达式。

变量的数字顺序是按照左括弧(出现的顺序定义的。考虑这样一个表达式:(ddbug (in) (domety)),请问\1,\2,\3保存的值分别为多少?给你一分钟时间考虑。

你是不是想回答\1=ddbug in domety,\2=in,\3=domety。你能这样回答已经算是掌握了后向引用的含意,但是这个回答是不正确的。为什么呢?我前面说过,这些变量保存的值是匹配结果,而不是表达本身。这个表达式还没有去匹配任何字符串,怎么会有结果呢。如果使用这个表达式去匹配ddbug in domety这样一个字符串,那么你的回答就是完全正确的。如果去匹配其它字符串,比如welcome to domety,那么变量的值就为空。

向左看 向右看

最后再介绍两个有点绕口的表达式,英文原名为lookahead和lookbehind,签于大家对向前和向后有不同的理解,我在这里把它们称作向右和向左。表达式的格式为:向右:(?=),向左(?<=)。应该怎么理解它们呢,它们不匹配任何字符,而是匹配一个位置。

比如我们只想查找domety中的do,可以这样写表达式:(?=domety)do。这个表达式的意思是:首先是一个位置,站在该位置向右看,可以看到domety,然后紧接着do。如果你用这个表达式去匹配domety,自然能成功,但是如果你用这个表达式去匹配字符串just do it,则会失败。因为该字符串中不存在这样一个位置。相反,如果我们想查找所有不在domety中的do,可以这样写表达式(?!domety)do,它的意思是:首先是一个位置,站在该位置向右看,看不到domety,然后紧接着do。该表达式可以匹配just do it但是不能匹配domety。

我们再来结合着向左看来查找domety中的me,表达式为:(?<=do)me(?=ty)它的意思是:首先是一个位置,该位置的左边是do,然后接着是me,然后又是一个位置,该位置的右边是ty。另外,(?<!)的意思相反,就是该位置的左边不是什么什么。

以上就是DDBug这一个星期来的学习总结,完成本文用时两天,在写作的过程对每一个正则表达式都经过反复的测试,确保正确。如果你对正则表达感兴趣,希望本文对你有所帮助。本文只是正则表达式一般的规则,也可以说是常用的规则。更高级的话题将在以后和大家讨论,因为我还没有理解透彻,所以就不在这里误人子弟了。

最后请记住,一定要多动手练习,用正则表达式去解决实际问题。