2009-02-21

正则表达式中与括号相关的特性

以下的例子用echo和grep命令来进行测试. echo后面的参数是目标,grep -P 后的参数是模式. -P参数的意思是模式是Perl格式的模式.
  1. 注释 (?#注释)
    Administrator@snow ~
    $ echo hello | grep -P '(?#this is a comment)hello'
    hello

    上例中,在括号之间的是注释,不作为模式的一部分
  2. 不保存的分组(non-capturing group) (?:group)
    Administrator@snow ~
    $ echo hellohihello | grep -P '(hello)(?:hi)\1'
    hellohihello

    上例中,虽然hi被包括在括号中,但是这对括号内的内容不会被保存,所以后面仍可用数字1来引用前面的hello
  3. 原子组(atomic group) (?>atomicgroup)
    Administrator@snow ~
    $ echo abcc | grep -P 'a(bc|b)c'
    abcc

    Administrator@snow ~
    $ echo abc | grep -P 'a(bc|b)c'
    abc

    Administrator@snow ~
    $ echo abcc | grep -P 'a(?>bc|b)c'
    abcc

    Administrator@snow ~
    $ echo abc | grep -P 'a(?>bc|b)c'


    原子组的意思是,组内(即括号内)的模式一旦匹配,正则表达式引擎就不会尝试组内的模式的其他组合。

    上面的例1和例2不是原子匹配,它能匹配abcc和abc。例3例4用的是原子匹配,它只能匹配abcc。在例2中,a(bc|b)c的a匹配abc中的a,然后括号内的bc匹配abc中剩下的bc,但是正则表达式引擎发现括号后的c没东西匹配了,于是它放弃bc的匹配,转而尝试括号内的b,它把这个b与abc中的b匹配,然后括号后的c成功与abc中剩下的c匹配。

    例4则不同。在例4中,模式中的a同样与abc的a匹配,然后正则表达式引擎在成功把括号内的bc与abc的bc匹配,再之后,它发现模式里剩下的c没有东西匹配了,因为它是原子匹配,组内的模式一旦匹配,正则表达式引擎就不会尝试组内的模式的其他组合,在这个例子中,即不会像例2那样尝试括号内的c这个组合,于是匹配失败。简单地说,即正则表达式引擎在这个时候不会回溯(no trackback)。

    原子组有时可以用于优化正则表达式。例如,在integers寻找匹配时,使用 \b(?>integer|insert|in)\b会比使用\b(integer|insert|in)\b来得快,因为后者试了三次才发现匹配不了,前者只要一次。

    在使用原子组的时候,必须要注意组内的选项位置。例如,\b(?>integer|insert|in)\b 匹配insert,而\b(?>in|integer|insert)\b不匹配。
  4. 前后查看(lookaround, lookahead, lookbehind)
    1. 向前查看(?=I hope there is something ahead) (?! I hope nothing is ahead)
    2. 向前查看让你可以“预先”查看目标的当前位置的前面有没有某样东西。“预先”的意思是它不消耗目标中的字符。例如,q(?!u)表示q后面不要跟着一个u。
      Administrator@snow ~
      $ echo qa | grep -P 'q(?!u)'
      qa

      Administrator@snow ~
      $ echo quick | grep -P 'q(?!u)'

      (?! )叫做negative lookahead。(?= )叫做positive lookahead。
      q(?=u)表示q后面必须跟一个u。它和qu(不使用向前查看)的区别是,它不消耗目标里的字符:
      Administrator@snow ~
      $ echo quick | grep -P 'q(?=u)uick' --color
      quick

      Administrator@snow ~
      $ echo quick | grep -P 'quuick' --color

      上面第1个例子括号外的u仍匹配quick里的u,括号里的u只查看一个“前面”有没有u。有则继续,无则返回假,并且把目标的开始位置向前移一位重新尝试。匹配的过程是:模式里的q与目标里的q匹配,然后模式的下一部分是一个向前查看,正则表达式引擎于是查看目标里的下一个字符是不是u。因为目标的当前字符是p,所以下一个字符是u,所以向前查看返回成功,匹配继续;模式的下一个部分是u,而因为向前查看不消耗目标的字符,所以目标的下一个字符还是u,与模式匹配的u匹配,接下来剩下的ick也匹配了。
    3. 向后查看(?<=I hope there was something left behind ) (?<!I hope nothing was left behind) 向后查看和向前查看差不多,只不过它是要求目标的当前位置的前面没有某些东西。例如,(?<!a)b要求b前面不要跟着一个a,所以它能匹配bed,而不能匹配cab: Administrator@snow ~ $ echo cab | grep -P '(?<!a)b' --color -o Administrator@snow ~ $ echo bed | grep -P '(?<!a)b' --color -o b 同样,向后查看也是不消耗目标里的字符的。上例1的匹配过程是:模式的当前位置是一个向后查看,正则表达式引擎于是查看有没有一个a,但是目标的当前位置是第一个字符,没有字符供它向后查看,即"后面"没有a,所以向后查看返回真(没有a时返回真),匹配继续.模式下一个位置是b,但是目标的当前位置是第一个字符c,与模式不匹配,于是目标的当前位置向前移动到第二个字符a,而模式的当前位置重新回到第一部分的向后查看,引擎查看到刚才舍弃了的c,不是a,向后查看成功;模式于是来到第二个位置,即那个b,它与目标的当前位置的a不匹配,于是目标的当前位置又向前移动一位,而模式又回到了向后查看;这一次,向后查看看到了一个a,但是这里希望的是不要有a,于是向后查看返回失败,目标再次向前移动。这时已移到了末尾,所以最后匹配失败。
    在使用前后查看时要注意,它们的括号分成的组也是原子组。这一点只有在前后查看里有分组的时候才注意。例如(?=(\d+))\w+\1永远匹配不了123x12。还有有的正则表达式引擎不支持在向后查看里使用数量符(+,?,*等),即要求向后查看里的必须是定长的。.NET则没有要求。
  5. 条件判断 (?ifthen|else)
    如果if返回真,则这个模式用then部分,否则用else部分。if部分使用前后查看正合适,因为它们可以返回真假,并且它们本身的括号正好用于区分if和then部分;另外,也可以使用分组的数字来作if部分(见下面例子)。then和else部分可以使用任何的正则表达式。
    Administrator@snow ~
    $ echo abd | grep -P '(a)?b(?(1)c|d)'
    abd

    Administrator@snow ~
    $ echo bc | grep -P '(a)?b(?(1)c|d)'

    在例2中,模式中的(a)?部分找不到匹配,于是后面的(1)返回假,所以模式就选择了else部分,即d,但是目标里的是c,所以匹配失败。 这里的1就是分组的数字.
  6. 用来开关一些选项
    Administrator@snow ~$
    echo TEst | grep -P '(?i)te(?-i)st'
    TEst

    Administrator@snow ~$
    echo teST | grep -P '(?i)te(?-i)st'


    Administrator@snow ~$
    echo TEster | grep -P '(?i)te(?-i:st)ER'
    TEster

    上面的例1和例2用(?i)来打开忽略大小写的开关,用(?-i)来关闭忽略大小写的开关.例3中,第2个括号临时关闭忽略大小写的开关,使得模式中的te和ER是忽略大小写(case insensitive),而st是对大小写敏感的(case sensitive).

转载请注明出处 http://fornote.blogspot.com/

没有评论:

发表评论