首页 > 网站 > WEB开发 > 正文

第十九章:正则表达式的模式匹配

2024-04-27 14:09:12
字体:
来源:转载
供稿:网友

第十九章:正则表达式的模式匹配

正则表达式,是一个描述字符模式的对象。javascript的RegExp类表示正则表达式,String和RegExp都定义了方法,后者使用正则表达式进行强大的模式匹配和文本检索与替换功能。Javascript的正则表达式是perl5的这种表达式语法的大小子集,所以多有perl编程经验的程序员来说,学习javascript的正则表达式是小菜一碟。

本章首先介绍用以描述“文本模式”的正则表达式语法。随后讲解了使用表达式String和RegExp方法。

1.正则表达式的定义

javascript中的正则表达式用RegExp对象表示,可以使用RegExp()构造函数来创建RegExp对象,不过RegExp对象更多是通过一种特殊的直接语法量来创建;就像通过引号包裹字符的方式来定义字符串常量一样,这种表达式直接量定义为包含在一堆斜杠(/)之间的字符。例如:

var patten = /s$/;

运行这段代码创建一个新的RegExp对象,并将它赋值给patten.这个特殊的RegExp对象用来匹配以"s"结尾的字符串。用构造函数也RegExp()也可以定义个与之等价的正则表达式,代码如下:

var patten = new RegExp( "s$" );

RegExp直接量和对象的创建

就像字符串和数字一样,程序中每个取值相同的原始类型直接量均表示相同的值,这是显而易见的。程序运行时每次遇到对象直接量(初始化表达式)诸如{}和[]的时候都会创建新对象。比如,如果在循环体中写 var a = [],则每次遍历都会创建一个新的空数组。

正则表达式直接量与此不同,ECMAscript3规定,一个正则表达式直接量会在执行到它时转化为一个RegExp对象,则同一段代码所表示正则表达式直接量的每次运算都返回同一个对象。ECMAScript5做了相反的规定,同一段代码所表示的这种表达式直接量每次运算都返回新对象。IE一直都是按照ECMAScript5规范实现的。

正则表达的模式规则是由一个字符序列组成的。包括所有字母和数字在内,大多数的字符都是按照直接量仅描述待匹配的字符的。比如说来,正则表达式/java/可以匹配任何包含“java”子串的字符串。除此之外,正则表达式中还有其他具有特殊语义的字符,这些字符并不按照字面含义进行匹配,比如正则表达式/s$/包含两个字符,第一个字符“s”按照字面含义匹配,第二个字符$是一个具有特殊意义的元字符。用以匹配字符串的结束。因此,这个正则表达式表示可以以任何“s”结尾的字符串。

接下来几节,讲述javascript正则表达式中使用各种字符和元字符。

i.直接量字符

正如上文所提到的,正则表达式中所有字符和数字都是按照字面含义进行匹配的。javascript正则表达式语法也支持飞字母的字符匹配,这些字符需要通过反斜线(/)作为前缀进行转转义。比如,转义符 /n用以匹配换行符。 下面李处了这些转义字符。

javascript正则表达式中的直接量字符

字符匹配
字母和数字字符自身
/oNUL字符(/u0000)
/t制表符(/u0009)
/n换行符(/u000A)
/v垂直制表符(/u000B)
/f换页符(/u000C)
/r回车符(/u000D)
/xnn由十六进制数指定的拉丁字符,例如/x0A等价于/n
/uxxxx由十六进制数xxxx指定的Unicode字符,例如/u0009等价于/t
/cX控制字符^X,例如 ,/cJ等价于换行符/n

在正则表达式中,许多标点符号有特殊的含义,它们是

^ $ . * + ? = ! : | / / ( ) [ ] { }

在接下来的几节里,我们将学习这些符号的含义,这些些符号只有在正则表达式的某些上下文才有特殊含义。在其他上下文中则被当成直接量处理。然而想在正则表达式中使用这些字符的直接量进行匹配,则必须使用前缀/,这是一条通行规则。其他标点符号(比如@和引号)没有特殊含义,在正则表达式中按照字面量含义进行匹配。

如果不记得那些标点符号需要反斜线转义,可以在每个标点符号前都加上反斜线。另外需要注意,许多字面和数字在反斜线前做前缀也有特殊含义,所以,对于想按照直接量进行匹配的字面和数字,尽量不要用反斜线进行转义。当前,想要在正则表达式中按照直接量匹配反斜线本身,则必须使用反斜线将其转义。比如,正则表达式“////”用以匹配任何包含反斜线的字符串。

ii.字符类

将直接量字符单独放进方括号内疚成了字符类(character class)。一个字符类可以匹配它包含的任意字符。

因此,正则表达式/[abc]/就和字母"a"、“b”、“c”中的任意一个都匹配。

另外,可以通过"^"来定义否定字符类,它匹配不包含在方括号内的字符。定义否定字符类时,将一个"^"作为左方括号内的第一个字符。正则表达式/[^abc]/匹配是的“a”、“b”、“c”之外的所有字符。

字符类可以使用连字符来表示字符的范围。要匹配拉丁字母表中的小写字母,可以使用/[a-z]/,要匹配拉丁字母表中任何字母和数字,则使用/[a-zA-Z0-9]/。

由于某些字符类非常常用,因此在javascript的正则表达式语法中,使用这些特殊字符的转义来表示它们。例如,/s匹配空格符、制表符和其它Unicode空白符。/S匹配的是非Unicode空白符的字符。下面列出了这些字符,并且总计了字符类的语法。(注意,有些字符类转义只能匹配ASCII字符,还没有扩展到可以处理Unicode类字符,例如/[/u0400-/u04FF]/用以匹配所有的Cyrillic字符(是一种斯拉夫语字母))。

正则表达式的字符类

字符匹配
[...]方括号内的任意字符
[^...]不在方括号内的任意字符
.除换行符和其它Unicode行终止符之外的任意字符
/w任何ASCII字符组成的单词,等价于[a-zA-Z0-9]
/W任何不是ASCII字符组成的单词,等价于[^a-zA-Z0-9]
/s任何Unicode空白符
/S任何非Unicode空白符的字符,注意/w和/S不同
/d任何ASCII数字,等价于[0-9]
/D 除了ASCII数字之外的任何字符,等价于[^0-9]
[/b]退格直接量(特例)

注意,在方括号之内也可以写成这些特殊转义字符。比如,由于/s匹配所有的空白字符,/d匹配的是所有数字,因此,/[/s/d]/就是匹配任意空白或数字。

注意,这里有一个特例,我们将会看到,转义符/b具有特殊含义,当用在字符类中时,它表示的是退格符,所以要在正则表达式中按照直接量表示一个退格符,只需要使用具有一个元素的字符类/[/b]/

iii.重复可以把两位数描述成//d/d/,四位数描述成//d/d/d/d/.但到目前为止,还没有一种方法可以用来描述多位数字,或者描述三个字母和一个数字构成的字符串。这些正则表达式语法中较为复杂的模式都提到了正则表达式中某元素的“重复出现次数”。

我们在正则模式之后跟随用以指定字符重复的标记。由于某些重复种类非常常用,因此,就有一些专门用于表示种植情况的特殊字符。例如:“+”用以匹配前一个或多个副本。下面的表总结了表示重复的正则语法。

正则表达式的重复字符语法

字符含义
{n,m}匹配前一项至少n次吗,但不能超过m次
{n,}匹配前一项n次或者更多次  
{n}匹配前一项n次  
?匹配前一项0次或1次,也就是说前一项是可选的,等价于{0,1}
+  匹配前一项1次或多次,等价于{1,}
*匹配前一项0次或多次,等价于{0,}

这里有一些例子:

    //d{2,4}/         //匹配2~4个数字    //w{3}/d?/        //精确匹配三个单词和一个可选的数字    //s+java/s+/      //匹配前后带一个或多个空格的字符串“java”    /[^(]*/           //匹配一个或多个非左括号的字符

在使用"*"和"?"时要注意,由于这些字符可能匹配0个字符,因此它们允许什么都不匹配。例如正则表达式/a*/实际上与字符串"bbbb"匹配,因为这个字符串含有0个a。

非贪婪的重复

上表中匹配重复字符是尽可能多地匹配,而且允许后继的正则表达式继续匹配。因此,我们称之为“贪婪的”匹配。我们同样可以使用这种表达式进行“非贪婪”的匹配。只需在待匹配的字符后跟随一个问号即可:“??”、“+?”、"*?"或"{1,5}"。比如,正则表达式/a+/可以匹配一个或多个连续的字母a。当使用"aaa"作为匹配的字符串时,正则表达式会匹配它的三个字符。但是/a+?/也可以匹配一个或多个连续字母a,但它是尽可能少的匹配。我们同样将"aaa"作为匹配字符串,但最后一个模式只能匹配第一个a。

使用非贪婪的匹配模式所得到的结果可能和期望的并不一致。考虑以下正则表达式/a+b/,它可以匹配一个或多个a,以及一个b。当使用它来匹配"aaab"时,你期望他能匹配一个a和最后一个b。但实际上,这个模式却匹配了整个字符串。现在再试一下非贪婪的匹配版本/a+?b/,它匹配尽可能少的a和一个b,当它用来匹配"aaab"时,你期望它能匹配一个a和最后一个b。但实际上,这个模式却匹配了整个字符串,和该模式的贪婪模式一模一样。这是因为正则表达式的匹配模式匹配总是会寻找字符串中第一个可能匹配的位置。由于该匹配是从字符串的第一个字符开始的,因此在这里不考虑它的子串中更短的匹配。

iiii.选择、分组和引用正则表达式的语法还包括指定选择项、子表达式分组和引用前一子表达式的特殊字符。字符"|"用于分隔供选择的字符。例如/ab|cd|ef/可以匹配字符串"ab",也可以匹配字符串"cd",还可以匹配字符串"ef"。/d{3}|[a-z]{4}/是三位数字或4个小写字母。

注意,选择项的尝试匹配总是从左到右,直到发现了匹配项。如果左边的选择项匹配,就忽略右边的匹配项。即使它产生更好的匹配。因此,当正则表达式/a|ab/匹配字符串"ab"时,它只能匹配第一个字符。

正则表达式中的圆括号有多种作用。一个作用是把单独的项组合成子表达式,以便可以像处理一个独立的单元那样用"|"、“*”、“+”或者"?"等来对单元内的选项进行处理。例如/java(script)?/可以匹配字符串"java",其后可以有"script"也可以没有。/(ab|cd)+|ef/可以匹配字符串"ef",可以匹配字符串"ab"或"cd"的一次或多次重复。

在正则表达式中,圆括号的另一个作用是在完整的模式中定义子模式,当一个正则表达式成功地和目标字符串相匹配时,可以从目标串中抽出和圆括号中的子模式相匹配的部分(本章最后看到如何取得这些匹配的子串)。例如,假定我们正在检索的模式是一个或多个小写字符后面跟随了一位或多位数字,则可以使用模式/[a-z]+/d+/。但假定我们真正关心的是每个匹配尾部的数字,那么如果将模式的数字部分放在括号中(/[a-z]+(/d)/),就可以从检索到的匹配中抽取数字了,之后更有详细的介绍。

带圆括号的表达式的另一个用途是允许在同一正则表达式的后部引用前面的子表达式。这是通过在字符“/”后加一位数字来实现的。这个数字指定了带圆括号的字表达式在正则表达式中的位置。例如,/1应用的是第一个带圆括号的子表达式,/3引用的是第三个带圆括号的子表达式。注意,因为子表达式可以嵌套另一个子表达式,所以它的位置是参与计数的左括号的位置。例如,在下面的正则表达式中,嵌套的子表达式([Ss]cript)可以用/2来指代

    /([Jj]ava[Ss]cript)?)/sis/s(fun/w*)/

对正则表达式中前一个子表达式的引用,并不是对子表达式模式的引用,而是指的是与那个模式相匹配的文本的引用。这样,引用可以用于实施一条约束,即一个字符串各个单独部分包含的是完全相同的字符。例如,下面的正则表达式匹配的就是位于单引号或双引号之内的0个或多个字符。但是,它不要去左侧和右侧的引号匹配,(即,加入的两个引号都是单引号或都是双引号):

/['"][^'"]*['"]/

如果要匹配左侧和右侧的引号,可以使用如下的引用:

    /(['"])[^'"]*/1/

/1匹配的是第一个带圆括号的子表达式所匹配的模式。在这个例子中,存在这样一条约束,那就是左侧的引号必须和右侧的引号相匹配。正则表达式不允许用双引号括起的内容有单引号,反之亦然。不能再字符类中使用这种引用。所以,下面的写法是非法的

    /(['"])[^/1]*/1/

在本章的最后几节中,我们会看到不用创建带数字编码的引用,也可以对子表达式进行分组。它不是以"("和")"进行分组,比如,考虑下面的这个模式

    /([Jj]ava(?:[Ss]cript)?)/sis/s(fun/w*)/

这里,子表达式(?:[Ss]cript)仅仅用于分组,因此,复制符号"?"可以应用到各个分组。这种改进的圆括号并不生成引用。所以在这个表达式中,/2引用了与(fun/W*)匹配的文本。

下表对正则表达式选择、分组和引用运算符做了总结

正则表达式的选择、分组和引用字符

字符含义
|选择,匹配的是该符号左边的子表达式或右边的子表达式
(...)组合,将几个项组合成一个单元,这个单元可通过"*"、“+”、“?”和"1"等符号加以修饰,而且可以记住和这个组合相匹配的字符串以供此后的引用和使用
(?:...)只组合,将项目合并到一个单元,但不记忆与改组相匹配的字符
/n和第n个分组第一次匹配的字符相匹配,组是圆括号中子表达式(也有可能是嵌套的),组索引是从左到右的左括号数,"(?:"形式的分组不编码。

iiiii.指定匹配的位置

正如前面所介绍的,正则表达式中的多个元素才能够匹配字符串的一个字符。例如,/s匹配的只是一个空白符 。还有一些正则表达式匹配的字符之间的位置,而不是实际的字符。例如,/b匹配一个单词的边界,即位于/w(ASCII单词)字符和/W(非ASCII单词)之间的边界,或位于一个ASCII单词与字符串开始或结尾之间的边界。像/b这样的元素不匹配某个可见的字符,它们指定匹配发生的合法位置。有时,我们称这些元素为正则表达式的锚,因为他们将模式定位在搜索字符串的特定位置上。最常用的锚元素是^,它用来匹配字符串的开始,锚元素$用来匹配字符串的结束。

例如,要匹配单词“JavaScirpt”,可以使用正则表达式/^JavaScript$/。如果想匹配"Java"这个单词本身(不像在“JavaScript”做单词的前缀),可以使用//sJavas/,可以匹配前后都有空格的单词“Java”。但是这样做有两个问题,第一。如果"Java"出现在字符串的开始或者结尾,就匹配不成功,除非开始和结尾处各有一个空格。第二个问题是,当找到了与之匹配的字符串时,它返回的匹配字符串的前端和后端都有空格。因此,我们使用单词的边界/b来代替真正的空格符

发表评论 共有条评论
用户名: 密码:
验证码: 匿名发表