zoukankan      html  css  js  c++  java
  • 正则表达式总结

    源文档借鉴    正则详细说明文档

    入门:正则字符

    .匹配不包括换行的任意字符

    在php的s修饰符(单行模式)下面可以匹配换行,如$pattern='#

    (.*?)

    #s';就可以匹配div内容有换行的数据。

    如果需要匹配包括换行的任意字符,可以使用[sS]代替.

    s空格、tab、换行

    [sS]表示匹配任意字符,S是s的反义。 注意区分[sS]与.的区别。

    *匹配零个或更多个,即0~n

    +匹配一个或更多个,即至少一个,1~n

    转义

    一个特殊字符前加就表示转义,说明把它当普通字符用

    []单字符取一个,比如[abc]会匹配a或b或c

    但是,如果[]里面加上^则会变成排除这个字符,如[^abc]就表示不是a、不是b、也不是c 另外,在[]里面可以使用-表示一个范围,如[0-9]表示从0到9,类似的还有[a-zA-Z],如果要包含-字符,可以给它加上转义[-]。

    关于[]常见的错误用法是:[ab|bc]用来表示ab或bc,实际上,它得到的结果是[abc|],即a或b或c或|这4个字符(单字符)的任意一个。这里可以改成(ab|bc)。

    总结:[]里面的特殊符有五个:[]-^,其他字符都是普通字符,包括*.?等。

    说明:

    • ^在[^ 的首位时候才有特殊意义
    • [0-9 -在不是首尾的时候有特殊意义
    • [ ] 因为占用[] 本身字符,所以有特殊意义
    • 本身是转义符,有特殊意义

    $字符串结束

    {1,3}循环次数

    [0-9]{1,3}表示在0-9的范围里面循环1个、2个或者3个,可能结果有5、20、415等。

    如果循环指定次数,如3次,则{3,3}可以简写成{3}。

    如果刚好需要匹配字符{1},则正则需要给{进行转义,得到{1}的正则。

    如果{}中间不是数字,则{}本身不需要转义。

    ?有两个用法

    (1)匹配一个或零个

    比如https?匹配的https(一个s)或者http(零个s)

    (2)非贪婪模式

    所谓非贪婪模式,就是匹配尽可能少的内容,比如,对于源字符串

                     
                   <div>a</div><div>b</div>
                
    使用<div>(.*?)</div>会得到2个结果(注意:如果源字符串有换行,使用[sS]替换 . ):
    
                     <div>a</div>
                
    
                  <div>b </div>
                

    因为,当遇到第一个 </div>,非贪婪模式就不会再往后找了。 而使用<div>(.*)</div>(贪婪模式)则会得到整个字符串,因为它会匹配所有字符直到后面再找不到 </div>。

    |多个数据选一(常用于多字符)

    前面提到[]里面的字符有选一个字符功能,但是假如不是一个字符,比如:http|ftp|svn 就需要用|分开,|的作用域是一直往后直到遇到括号,比如,对于源字符串

    • http abc
    • ftp abc
    • svn abc

    http|ftp|svn abc匹配的结果是

    http

    ftp

    svn abc

    想要匹配 http abc和ftp abc和svn abc就要使用括号把前边的协议括起来,如(http|ftp|svn) abc 可以得到预期的结果。

    ()数据分界和取数据

    上面例子(http|ftp|svn) abc就是数据分界的例子,然后,匹配结果会得到一个[1]的子集数据(数组下标1),这里就是子模式的概念,子模式也叫分组,利用子模式,可以得到想要取出来的数据。子模式1、2、3的计算方法为左括号的计数,从左到右,从1开始,比如:

    (http|ftp|svn)://([^/]+),分组1得到的是(http|ftp|svn)里面的数据,分组2得到([^/]+)里面的数据,对于嵌套括号也是点左括号即可。在正则中有很多与括号结合的写法,你在数左括号的时候,一定要注意,非捕获组和环视的左括号都是不需要数的。

    在使用子模式过程中,常见两种写法是:1 和 $1。

    • (1) 1 是在正则表达式本身中引用分组1的内容,如: 我们要匹配111这样的连续出现3此的数字,我们可以写出正则:(d)11,(d)匹配到第一个1,后面再引用这个匹配内容,得到111。
    • 2) $1 是在替换中调用分组的内容,如:
      我们要替换链接参数name=Zjmainstay为username=Zjmainstay,我们可以使用正则name=([^&]+)替换为username=$1来实现,这里的$1就引用了分组1的结果Zjmainstay,因此得到我们想要的结果。

    (?:)非捕获组

    上面说到()作为子模式可以得到它里面的数据,但是,有些时候,()只是作为数据分界功能,并不需要取出来,这时候就要用到非捕获组的概念了。比如:(http|ftp|svn)://([^/]+)只想得到域名,也就是[2],那么(http|ftp|svn)就只是数据分界的功能,这里不需要捕获,因此使用非捕获组功能,(?:http|ftp|svn)屏蔽这部分的数据获取,此时,(?:这个左括号排除[1]计数,也就是(?:http|ftp|svn)://([^/]+)中的([^/]+)变成[1]了。

    分隔符

    在一些语言中,你会发现正则第一个和最后一个字符是相同的,如:

    /d+/

    这个/ /在PHP中称为分隔符,正则表达式需要由分隔符闭合包裹。在PHP中,分隔符可以使任意非字母数字、非反斜线、非空白字符。这个概念很关键,它能帮助我们简化一些正则的书写,避免错误,如:

    
                  /<div>.*?</div>/ 
                

    这个正则是错误的。

    原因是 </div>的/与分隔符相同,但是却没有做转义。

    对于这种情况,有两种解决方案:

    • /<div>.*?</div>/
    • #<div>.*?</div>#

    方案(1)是对正则内部的分隔符做转义,方案(2)是规避了本来的/分隔符,使用#作为分隔符,从而避免/需要转义。

    虽然很多情况下,都是看到前后一致的分隔符,但是,大家需要了解一下,[<div>.*?</div>]这个表达式在PHP里也是合法的。(不提倡使用,仅了解!)

    模式修饰符

    模式修饰符在许多程序语言中都支持的,比如最常见的是i,不区分大小写,如javascript里的/[a-z0-9]/i,表示匹配字母数字,不区分大小写。

    本人在写php正则时常用的模式修饰符主要有i和s,如: $pattern = '#[a-z0-9]+#is';

    模式修饰符s的作用主要是的.能够匹配换行,在处理换行数据时,通常会用到。

    在PHP中,模式修饰符有两种用法,一种是上面的,在分隔符后面的模式修饰符,它的作用范围是全局;另一种是在正则表达式中间的。

    例如:
    
              正则:/((?i)[A-Z]+)c/
            测试字符:abcABC
            匹配:abc
            说明:局部(ab)的大小写被忽略了,(?i)的作用范围在分组1内
                

    如果把正则改成:/([A-Z]+)c/i,则匹配结果将是:abcABC

    按单字符匹配

    正则里面的数据都是按照单个字符来进行匹配的,这个通过数值区间的例子最容易体现出来,比如,示例一:

    我要匹配0-15的数值区间,用正则来写的话,便是[0-9]|1[0-5],这里,便是把0-9这种单字符的情况,和10-15这种多字符的情况拆分开了,使用分支|来区分开,表示要么是0-9,要么是10-15。

    上面是两位数值的情况,现在延伸至1-65535,我个人的处理思想是从大到小,一块块分解:

    
            1. 65530-65535  ==>  6553[0-5]          末位区间0-5
            2. 65500-65529  ==>  655[0-2][0-9]      第四位区间0-2,末位区间0-9
            3. 65000-65499  ==>  65[0-4][0-9]{2}    第三位区间0-4,后两位0-9
            4. 60000-64999  ==>  6[0-4][0-9]{3}     第二位区间0-4,后三位0-9
            5. 10000-59999  ==>  [1-5][0-9]{4}      第一位区间1-5,后四位0-9
            6. 1-9999       ==>  [1-9][0-9]{0,3}    第一位只能是1-9,后三位可有可无
                

    最后组合起来:

    
              (6553[0-5]|655[0-2][0-9]|65[0-4][0-9]{2}|6[0-4][0-9]{3}|[1-5][0-9]{4}|[1-9][0-9]{0,3}) 
                

    便得到1-65535匹配正则。

    根据数据处理需求,可以在正则前后加上^和$,以匹配整个数据串,或者前后加入,把它当做单词边界处理。没有限定字符的边界往往是js正则判断中常见的错误之一。

    贪婪模式与非贪婪模式

    正则的贪婪模式和非贪婪模式是一个比较容易混淆的概念,不过,我们可以这么理解,一个人很贪婪,所以他会能拿多少拿多少,换过来,那就是贪婪模式下的正则表达式,能匹配多少就匹配多少,尽可能最多。而非贪婪模式,则是能不匹配就不匹配,尽可能最少。

    
            需求:匹配1后面跟任意个0
            源串:10001
            使用贪婪模式:10*       结果:1000 和 1
            使用非贪婪模式:10*?    结果:1 和 1
                

    首先,*是匹配0个或多个的意思。

    贪婪模式下,它表示,首先匹配一个1,然后匹配1后面的0,最多可以匹配3个0,因此得到1000,然后第二次又匹配到一个1,但是后面没有0,因此得到1;

    非贪婪模式下,它表示,首先匹配一个1,然后1后面的0,能不匹配就不匹配了,所以,它每次都只是匹配了一个1。

    那么,下来我们改改

    
            需求:匹配1后面跟任意个0,再跟一个1
            源串:10001
            使用贪婪模式:10*1       结果:10001
            使用非贪婪模式:10*?1    结果:10001
                

    为什么这两次的结果一样了?

    因为,正则表达式要判断完这整个正则才算成功,这种情况下

    贪婪模式,首先匹配一个1,然后匹配1后面尽可能多的0,发现3个,再匹配0后面的一个1,正则表达式匹配完,完成匹配,得到10001;

    非贪婪模式,首先匹配一个1,然后,0*?是非贪婪模式,它不想匹配了(非贪婪模式不匹配优先),看看后面是不是1了?然后发现哎妈呀,后面是个0啊,然后它回头,不能再偷懒了,用0*?匹配一个0吧,然后匹配到10,又不想匹配了,看看后面有没有1了,还是没有,又回去用0*?匹配掉一个0,得到100,继续偷懒,但是发现后面还不是1,然后又用0*?匹配得到1000,最后,发现真不容易啊,终于看到1了,匹配得到10001,正则表达式匹配完,完成匹配。

    那究竟哪个模式好呢?

    下面我举个例子:

    
                    <a href="http://www.zjmainstay.cn/my-regexp" target="_blank" title="我眼里的正则表达式(入门)">我眼里的正则表达式(入门)</a>
                     <a title="我眼里的正则表达式(入门)" href="http://www.zjmainstay.cn/my-regexp" target="_blank">我眼里的正则表达式(入门)</a>
                

    正则1:<a [^>]*?href="([^"]*?)"[^>]*?>([^<]*?)</a>(238次)

    正则2:<a [^>]*?href="([^"]*)"[^>]*>([^<]*)</a>(65次)

    附:执行次数的获取请下载正则表达式测试工具: RegexBuddy 4.1.0-正则测试工具.rar

    正则1是通用写法,正则2是在确定字符不会溢出的情况下消除非贪婪模式

    此,关于贪婪模式好还是非贪婪模式好的讨论,只能说根据需求而定,不过,在平时的时候用,一般使用非贪婪模式较多,因为贪婪模式经常会由于元字符范围限制不严谨而导致匹配越界,得到非预期结果。

    在确定的数据结构里,可以尝试使用[^>]*>这样的排除字符贪婪模式替换非贪婪模式,提升匹配的效率。注意,贪婪部分([^>]*)的匹配,最好不要越过其后面的字符(>),否则会导致贪婪模式下的回溯,如正则3,[^>]*的匹配越过了href,一直匹配到>为止,而这时候再匹配href,会匹配不到而导致多次回溯处理,直到回溯到href前的位置,后面才继续了下去。

    环视(断言/零宽断言)/先前 先后 预搜索

    环视,在不同的地方又称之为零宽断言,简称断言。

    用一句通俗的话解释

    环视,就是先从全局环顾一遍正则,(然后断定结果,)再做进一步匹配处理。

    断言,就是先从全局环顾一遍正则,然后断定结果,再做进一步匹配处理

    两个虽然字面不一样,意思却是同一个,都是做全局观望,再做进一步处理。

    环视的作用相当于对其所在位置加了一个附加条件,只有满足这个条件,环视子表达式才能匹配成功。

    环视主要有以下4个用法:

    • (?<=exp) 匹配前面是exp的数据
    • (?<!exp) 匹配前面不是exp的数据
    • (?=exp) 匹配后面是exp的数据
    • (?!exp) 匹配后面不是exp的数据

    示例四:

    (?<=B)AAA 匹配前面是B的数据,即BAAA匹配,而CAAA不匹配

    (?<!B)AAA 匹配前面不是B的数据,即CAAA匹配,而BAAA不匹配

    AAA(?=B) 匹配后面是B的数据,即AAAB匹配,而AAAC不匹配

    AAA(?!B) 匹配后面不是B的数据,即AAAC能匹配,而AAAB不能匹配

    另外,还会看到(?!B)[A-Z]这种写法,其实它是[A-Z]范围里,排除B的意思,前置的(?!B)只是对后面数据的一个限定,从而达到过滤匹配的效果。

    因此,环视做排除处理是比较实用的,比如,示例五:

    
     需求:字母、数字组合,不区分大小写,不能纯数字或者纯字母,6-16个字符。
    通用正则:^[a-z0-9]{6,16}$    字母数字组合,6-16个字符
        排除纯字母:(?!^[a-z]+$)
    排除纯数字:(?!^[0-9]+$)
        组合起来:(?!^[a-z]+$)(?!^[0-9]+$)^[a-z0-9]{6,16}$
                

    注意,环视部分是不占宽度的,所以有零宽断言的叫法。

    所谓不占宽度,可以分成两部分理解:

    1、环视的匹配结果不纳入数据结果

    2、环视它匹配过的地方,下次还能用它继续匹配。

    如果不是环视,则匹配过的地方,不能再匹配第二次了。

    上面示例四体现了:环视的匹配结果不纳入数据结果,它的结果:

    
    (?<=B)AAA     源串:BAAA  结果:AAA
    (?<!B)AAA     源串:CAAA  结果:AAA
    AAA(?=B)      源串:AAAB  结果:AAA
    AAA(?!B)      源串:AAAC  结果:AAA
                

    而示例五体现了:环视它匹配过的地方,下次还能用它继续匹配 因为,整个匹配过程中,正则表达式一共走了3次字符串匹配,第一次匹配不全部是字母,第二次匹配不全部是数字,第三次匹配全部是字母数字组合,6-16个字符。

    
     扩展部分:
    `[A-Z](?<=B)`   [A-Z]范围等于B
    `[A-Z](?<!B)`   [A-Z]范围排除B
    `(?!B)[A-Z]`    [A-Z]范围排除B
                

    js不支持(?<=exp) 和 (?<!exp) 语法

    模式修饰符

    模式修饰符在许多程序语言中都支持的,比如最常见的是i,不区分大小写,如javascript里的/[a-z0-9]/i,表示匹配字母数字,不区分大小写。

    模式修饰符s的作用主要是的.能够匹配换行,在处理换行数据时,通常会用到。

    在PHP中,模式修饰符有两种用法,一种是上面的,在分隔符后面的模式修饰符,它的作用范围是全局;另一种是在正则表达式中间的。

    
    正则:/((?i)[A-Z]+)c/
    测试字符:abcABC
    匹配:abc
    说明:局部(ab)的大小写被忽略了,(?i)的作用范围在分组1内
                

    如果把正则改成:/([A-Z]+)c/i,则匹配结果将是:abcABC

  • 相关阅读:
    初识spring
    关于导入别人的web项目,tomcat无法显示的问题
    doPost无法跳转显示信息,只能下载文件查看
    socket网络编程
    log日志文件
    第三方模块安装
    __name__ __doc__ __package__
    格式化
    导入模块
    python正则表达式补充
  • 原文地址:https://www.cnblogs.com/wwkk/p/9655935.html
Copyright © 2011-2022 走看看