上文里,我们介绍了正则表达式的子模式,逆向引用和量词,在这篇文章里,我们将重点介绍正则表达式中的断言(Assertions)。

  断言(Assertions)

  断言(Assertions)是在目标字符串的当前匹配位置进行的一种测试但这种测试并不占用目标字符串,也即不会移动模式在目标字符串中的当前匹配位置。

  读起来似乎有点拗口,我们还是举几个简单的例子。

  两个最常见的断言是元字符“^”和“$”,它们检查匹配模式是否出现在行首或行尾。

  我们来看这个模式/^\d\d\d$/,试着用它来匹配目标字符串“123”。“\d\d\d”表示三个数字字符,匹配了目标字符串的三个字符,而模式中的^和$分别表示这三个字符同时出现在行首和行尾,而它们本身并不与目标字符串中的任何字符相对应。

  其它还有一些简单的断言\b, \B, \A, \Z, \z,它们都以反斜线开头,前面我们已经介绍过反斜线的这个用法。这几个断言的含义如下表。

| 断言 | 含义 | | \b | 字分界线 | | \B | 非字分界线 | | \A | 目标的开头(独立于多行模式) | | \Z | 目标的结尾或位于结尾的换行符前(独立于多行模式) | | \z | 目标的结尾(独立于多行模式) | | \G | 目标中的第一个匹配位置 |

  注意这些断言不能出现在字符类中,如果出现了也是其它的含义,例如\b在字符类中表示反斜线字符0x08。

  前面介绍的这些断言的测试都是一些基于当前位置的测试,断言还支持更多复杂的测试条件。更复杂的断言以子模式方式来表示,它包括前向断言(Lookahead assertions)和后向断言(Lookbehind assertions)。

  前向断言(Lookahead assertions)

  前向断言从目标字符串的当前位置向前测试断言条件是否成立。前向断言又可分为前向肯定断言和前向否定断言,分别用(?=和{?!表示。例如模式/ \w+(?=;)/用来表示一串文本字符后面会有一个分号,但是这个分号并不包括在匹配结果中。一件有趣的事看起来差不多的模式/ (?=;)\w+/并不是表示一串前面不是分号的alpha字符串,事实上,不论这串alpha字符的前面是否是一个分号它总是匹配的,要完成这个功能需要我们下面提到的后向断言(Lookbehind assertions)。

  后向断言(Lookbehind assertions)

  后向断言分别用(?<=和(?

  使用后向断言与一次性子模式搭配使用可以有效的文本的结束部分进行匹配,这里来看一下例子。

  考虑一下如果用/abcd$/这样一个简单的模式来匹配一长段以abcd结尾的文本,因为模式的匹配过程是从左向右进行的,正则表达式引擎将在文本中寻找每一个a字符并尝试匹配剩余的模式,如果在这长段文本里仅好有不少的a字符,这样做明显是非常低效的,而如果把以上模式换成为样/^.*abcd$/,这时前面的“^.*”部分将匹配整个文本,然后它发现下一个模式a无法匹配,这时会发生前面提到过的回溯过程,解析器会逐次缩短“^.*”匹配的字符长度从右向左逐次查找剩余的子模式,也要产生多次的尝试过程。现在,我们用一次性子模式与后向断言重写所用的模式,改为/^(?>.*)(?<=abcd)/,这时,一次性子模式一次匹配了整段文本,然后用后向断言检查前面四个字符是否为abcd,只需要一次检测就可以立刻确定整个模式是否匹配。在遇到需要匹配一个很长的文本时,这种方法可以非常显著的提高处理效率。

  一个模式中可以包含多个相继的断言,断言也可以嵌套。另外,断言使用的子模式也是非捕获的,不能被逆向引用。

  断言的一个重要应用领域就是做为条件子模式的条件。那什么是条件子模式呢?

  条件子模式(Conditional subpatterns)

  正则表达式允许在模式中根据不同的条件使用不同的匹配子模式。也就是条件子模式(Conditional subpatterns)。它的格式如下:(?(condition)yes-pattern)或者 (?(condition)yes-pattern no-pattern)。如果条件满足,采用yes-pattern,否则,采用no-pattern(如果在模式中提供了话)。

  条件子模式中的条件有两种,一种是断言结果,另一种是看是否捕获一个前面提供的子模式。

  如果在表示条件的圆括号里的内容是一个数字,它表示当此数字代表的子模式被成功匹配时条件为真。看看下面这个例子,/( ( )? [^()]+ (?(1) ) )/x,(注意“x”模式修正符表示忽略字符类外的空白字符和#符号之后的内容)。

  这个模式的第一部分“( ( )?”匹配了一个可选的左图括号“(”,第二部分“[^()]+”匹配了一个以上的非圆括号字符,最后一部分“(?(1) ) )”是个条件子模式,表示如果捕获到\1也即那个可选的左圆括号,第三部分应该会出现一个右圆括号“)”。

  如果在表示条件的圆括号内是一个“R”字符,表示在这个模式或子模式被递归调用时条件为真,在递归调用的顶层,这个条件为假。关于正则表达式中的递归,我们会在后面的部分专题介绍。

  如果条件不是一个数字或R字符,则它必需是一个断言。断言可以是肯定或否定的前身或后向断言。让我们看下面这个例子。

  /(?(?=[^a-z]*[a-z])

  \d{2}-[a-z]{3}-\d{2} \d{2}-\d{2}-\d{2} )/x

  为了让这个正则表达式更容易阅读,我们特意采用了x模式修正符,这样我们可以在用模式中加入空格对符式进行格式上的分隔并分行表示而不影响模式的解析。

  第一行的条件子模式使用了一个肯定的前向断言,表示一串可选的非小写字母后面跟随着一个小写字母。换句话说,它查看目标字符串是否至少包含一个小写字母,如果是,它用“ ”前的模式对目标进行匹配,看目标是否为看目标是否为两个数字-三个小写字母-两个数字这种格式,否则,用“ ”来匹配目标,看目标字符串是否为由“-”分隔的三段二位十进制数字。

   正则表达式中的注释

  为了让正则表达式更容易阅读,可以在其中加入注释语句。通常注释由左圆括号和井号——“(#“开始,当遇到下一个右圆括号”)“结束。注释是禁止嵌套的。

  如果设定了“x”模式修正符,任何字符类之外(也即[]之外)的井号(#)和下一个新行标记之间的部分也被作为注释看待。

]]>