正则表达式简明示例教程

一、什么是正则表达式

正则表达式是一种人们为了解决某类问题而发明的工具,通过简(fu)单(za)的语法拼接一个用来匹配的字符串,然后应用于各种程序语言,进行结果的搜索匹配。以下的应用场景你是否遇到 ?

  • 搜索某个目录下的某些文件名?
  • 写代码过程中寻找、修改一些文件、方法名?
  • 判断手机号、邮箱、身份证号是否合法?
  • 判断一个字符串中是否有数字?
  • 提取一个网页源文件中的手机号?

等等场景,这里就不一一赘述,如果没有一个正确、有效的方法进行处理,将会是一件非常痛苦的事,这时候,如果你会了正则表达式,那么这一切将会很轻松了。
其实,正则很简单,只要你跟着敲一遍测试一遍,多理解理解不多的几种语法,也就够日常开发使用了。

二、如何测试正则表达式

不同语言对于正则表达式的兼容都是会有一些细微的差异,但是总归都差不多,学了以后都能用,如果要测试的话,可以使用如下两种方式,当然不止这两种:

  • oschina 的基于Javascript语法的正则表达式测试工具,传送门;
  • 我自己写的一个Mac端的基于Objective-C的正则表达式测试工具,传送门,下载以后运行一下就行了。

三、进入正题,正则表达式基础语法

1、元字符

  • . : 匹配除了换行符以外的任意单个字符
    如:n3]等字符

  • \w : 匹配字母、数字、下划线、汉字等单个字符,注意,只能是这几种中任意的单一字符

  • \s : 匹配任意单个的空白字符
    如:空格、tab、换行符等。

  • \d : 匹配单个的数字

  • \b : 匹配单词的开始和结束,单词的开始和结束指的是不能夹杂有空格或者匹配的前后有不是空格等字符。

测试字符串:My name is Joyous.,其中MyJoyous等就是有效的单词,可以进行匹配,如 \bname\b 可以匹配 name\bJoyous\b可以匹配Joyous
但是不能匹配ous,比如你写这样的正则\bous\b,这肯定是不行的。
在上面几个例子中,都用了前后两个用于匹配的元字符,其实也不一定非得用两个,有的时候为了匹配一段话也可以使用单独的前或后的元字符,比如\bname.*,可以进行如下匹配name is Joyous.,其中用到了*限定符,这个后面讲。

  • ^ : 匹配字符串的开始
  • $ : 匹配字符串的结束
    以上两个元字符一般用在字符串合法性的匹配等,比如^13\d{9}$用来匹配是否为13开头的手机号,如果匹配到结果那就是了,否则就不是了,还有开头说过的判断邮箱、身份证号等。
    这两个也不是非得一起使用,也可以单独拿出来使用。

2、限定符

  • * : 重复匹配0次或多次,就是传说中的贪婪匹配,只要符合要求就一直匹配下去
    比如1234567890,使用正则6\d*,这个表达式的意思就是匹配从6开始往后匹配数字直到不是数字的位置,最后一个匹配到的结果将会是67890。如果这么写0\d*那么将会匹配到0
    这回懂了么 ?

  • + : 类似上个限定符.,它的意思是重复匹配1次或多次,也是贪婪匹配,如果上个测试中使用0\d+这个正则去匹配的话,将不会匹配到任何内容,以为从0往后没有任何一个数字了。

  • ? : 匹配1次或0次,不会重复的匹配,用它修饰的元字符或者表达式分组,将会根据情况进行匹配或不匹配。
    这个是懒惰匹配,能不匹配了就不匹配了,比如匹配<a 328459 /> hello <a asd3479 sdf80 />,使用正则匹配每对标签<a.*?/>,将会匹配到每对括号内的内容,想一下如果使用了贪婪匹配将会是什么样的结果?

  • {n} : 重复匹配n次,指定了匹配的次数,比如手机号的位数是固定的,那就需要限定重复匹配的次数。

  • {n,} : 重复匹配n次或多次,这个类似于+限定符,只不过最少匹配次数由用户指定。

  • {n,m} : 重复匹配n次到m次,一个区间值,只要在这个区间内,都是可以匹配到的。

3、反义词

  • \W : 跟\w相反,匹配任意不是字母、数字、下划线、汉字的字符。

  • \S : 跟\s相反,匹配任意不是空白符的字符。

  • \D : 跟\d相反,匹配任意不是数字的字符。

  • \B : 跟\b相反,匹配不是单词开头或结束的位置。

  • [^x] : 匹配除了x以外的任意字符。
    比如说,英文中的元音字母aeiou,想匹配除了他们以外的所有字符,可以这样来写:[^aeiou],结果如何,大家可以自己试一下。

4、捕获

  • (exp) : 匹配exp,并捕获文本到自动命名的组里,exp可以是字符,也可以是正则表达式。
    在正则表达式匹配结果中,会自动的将整个的匹配结果命名为0组,如上面所有得了例子中都没有使用到分组,其实他们的结果都被自动分配到了0组里,于是,也就是说该正则表达式中剩下的自己分的组就得从1开始了。
    可以这样用,使用正则(abc).*?\1匹配asabcaasd9fabcsdf,将会匹配到abcaasd9fabc这段内容。后面的\1就是只分组1的内容。
    当然,你可以分更多的组。

  • (?<name>exp) : 匹配exp,并捕获文本到自己命名的name组里,使用前面的自己命名的组的话,注意使用\k<name>
    测试文本:0123hahaha2345678hahaha
    测试正则:(?<num>\d+).*?\k<num>
    整体匹配到结果是23hahaha23,在name组里捕获到的是23

  • (?:exp) : 匹配exp,不捕获匹配的文本,也不给此分组分配组号。下面左侧测试:
    测试文本:hello world hello world !
    测试正则1:(hello).*?\1,匹配结果是 hello world hello;
    测试正则2:(?:hello)\s+(world).*?\1,匹配结果是 hello world hello world;
    可以发现测试2中虽然前面有个分组,但是并没有分配组号,1分组分给了后面的分组去了

5、临宽断言

  • (?=exp) : 匹配exp前面的位置,比如:
    测试文本:My name is Joyous !
    测试正则:.+?(?=is)
    测试结果:My nameis及后面的字符串都没有了

  • (?<=exp) : 匹配exp后面的位置,比如:
    使用正则(?<=name).+测试上面的内容,匹配结果将会是is Joyous !

  • (?!exp) : 匹配后面跟的不是exp的位置。
    测试文本:from here to there 30m not 30.
    测试正则:\b(?!m)\d+\b
    匹配结果将会是后面的那个30,而不会匹配两个30

  • (?<!exp) : 匹配前面不是exp的位置,比如
    测试文本:America money $30 and China money 30
    测试正则:\b(?<!\$)\d+\b
    匹配结果将会是后面的那个30,而不会匹配两个30

  • (?#comment) : 注释,comment为注释内容,不会对匹配结果又影响。

四、示例

  • 匹配身份证号 : ^(\d{6})(\d{4})(\d{2})(\d{2})(\d{3})([0-9]|X)$,用了分组主要是为了需要进行安全验证
  • 匹配(年-月-日格式)日期 : ([0-9]{3}[1-9]|[0-9]{2}[1-9][0-9]{1}|[0-9]{1}[1-9][0-9]{2}|[1-9][0-9]{3})-(((0[13578]|1[02])-(0[1-9]|[12][0-9]|3[01]))|((0[469]|11)-(0[1-9]|[12][0-9]|30))|(02-(0[1-9]|[1][0-9]|2[0-8])))
  • 匹配数值 : ^[1-9]\d{0,}(\.\d+)?$
  • 匹配双字节字符(包括汉字) : [^\x00-\xff]
  • 匹配QQ号 : [1-9][0-9]{4,9}
  • 匹配网址 : (https://|http://)?([\w-]+\.)+[\w-]+(/[\w- ./?%&=]*)?
  • 匹配邮箱地址 :

    1
    [\w!#$%&\'*+/=?^_`{|}~-]+(?:\.[\w!#$%&\'*+/=?^_`{|}~-]+)*@(?:[\w](?:[\w-]*[\w])?\.)+[\w](?:[\w-]*[\w])?
  • 匹配整个网页 : <html.*?>\s+<head.*>[\s\S]+</head>[\s\S]+<body>[\s\S]+</body>[\s\S]+</html>

其他的待补充。。。