前言 前几天在空隙时间在读这本书。不愧是关于
正则表达式的一本好书,我也应该买一本的。现在是暂时借了杨威买的第三版的中文版来读。中文版翻译得还不错。有不少地方感觉翻译得挺精彩的,虽然马上就能猜到原文是怎么写的,但读起来中文一点都不觉得拗口。
现在才读到第76页,第二章,比较入门的部分。以前也不是没用过
正则表达式,不过一边读这书一边就觉得以前真的中了很多陷阱。而且我居然一直不知道
正则表达式里面有
lookaround(positive/negative lookahead/lookbehind)这种好用的东西……真糟糕。不过现在学到也还不算迟。
比较麻烦的是第二章里几乎全部代码例子都是写的,而我以前虽然没少用别人写的perl脚本,自己却几乎没写过perl代码。这下是领略到了不少perl的威力啊。
简单来说,像这样:
- $input = ; # get a line of input from stdin
- chomp( $input ); # cut the trailing newline character
-
- # verify the input as a decimal number
- if ( $input =~ /^([-+]?[0-9]+(?:\.[0-9]*)?)$/ ) {
- # verified
- $result = $input * 2;
- print "input multiplied by 2 is: $result";
- } else {
- # verification failed
- print "Expecting a number, so I don't understand \"$input\".\n";
- }
$input = ; # get a line of input from stdin
chomp( $input ); # cut the trailing newline character
# verify the input as a decimal number
if ( $input =~ /^([-+]?[0-9]+(?:\.[0-9]*)?)$/ ) {
# verified
$result = $input * 2;
print "input multiplied by 2 is: $result";
} else {
# verification failed
print "Expecting a number, so I don't understand \"$input\".\n";
}
在我看来就是语言的动态特性与
正则表达式的优美结合啊。($input的类型可以自动在String与number之间转换,而
正则表达式可以作用在String版本的类型上来检查输入的一行内容里是不是一个合法的数字。)
回头想想,我最初接触
正则表达式的时候是在
Java上用的。虽然编译原理课上也有讲到DFA/NFA与
正则表达式,但那个时候没用在C/C++里用过
正则表达式而只是实现过些简单的DFA。
Java里要用
正则表达式颇不顺手。最痛苦的莫过于转义字符的问题。那时我对
正则表达式里常用的一些预定义字符组之类的很不熟悉,只好阅读
Java标准库的
JavaDoc来学习。看看类的
JavaDoc,可以看到很多与转义字符相关的预定义字符组之类的东西,但我当时花了很长时间才理解到
Java本身字符串中的转义字符与正则表达式中的转义字符不是一回事——它们是叠加的。为了表达正则表达式里的\b,用Java应该写成"\\b"的字符串;为了匹配一个斜杠和句点“\.”而要写出正则表达式\\\.,在Java中却得写成"\\\\\\."。结果我经常在大量的“\”中迷茫,到底写了多少个“\”都数不清了……
接触到和有之后这转义的问题减轻了不少,不过要是能直接写
正则表达式的字面量而不是用字符串来表示
正则表达式就更舒服了。在阅读一些
JavaScript的相关文章时留意到有不少人提到“perl风格的
正则表达式记法”,查了下,发现这种以operation/regex/modifier为形式的记法确实好用。不过当时也是随便写了几个
JavaScript的小测试玩了玩就没管了,也没去看看这记法的来源——perl里它到底长什么样。这次就多记点
JavaScript里的perl-style regex吧。
====================================================================
笔记 在上有一套关于
JavaScript的教程,不过比较老了,是1997年12月4日最后编辑的。虽然文章比较老了,其中还是可以参考的;毕竟在v3之后就一直没正式发布更新的版本嘛。
JavaScript 1.8要跟随FireFox 3.0才会出现,而
JavaScript 2.0得等ECMAScript最终确定。漫长啊。
JavaScript 1.8的技巧似乎在有些记载,有空的时候要去看看。
等我买了新的犀牛书之后再结合ECMA-262来检查一下有没有些什么更新的地方好了……
Mozilla MDC有对应
JavaScript 1.5(带有1.8更新)的
正则表达式文档。
先来看点简单的例子吧。
在关注如何在
JavaScript操作
正则表达式之前,先看看如何定义一个
正则表达式。
与perl一样,
JavaScript中可以在一头一尾的两个斜杠(“/”)中间写一个
正则表达式的字面量。但具体的状况与perl又不完全相同:
perl:
引用
m/PATTERN/cgimosx
/PATTERN/cgimosx
q/STRING/
'STRING'
qq/STRING/
"STRING"
qr/STRING/imosx
qx/STRING/
`STRING`
qw/STRING/
s/PATTERN/REPLACEMENT/egimosx
tr/SEARCHLIST/REPLACEMENT/cds
y/SEARCHLIST/REPLACEMENTLIST/cds
JavaScript:
引用
/PATTERN/gimy
JavaScript中的
正则表达式字面量只能以斜杠开头,而不能像perl里一样在斜杠前指定操作。默认的操作就是m/(match)。在结尾的斜杠后面可以像perl里一样,指定一些修饰符;但不及perl所提供的修饰符号多,只支持以下几种:
g
全局匹配
i
忽略大小写
m
多行匹配
y
FireFox 3/
JavaScript 1.8中添加的新修饰符。只从该
正则表达式实例的lastIndex属性开始搜索。
在ECMAScript 4的草案中,有提议采纳/x修饰符以支持perl风格的多行
正则表达式记法,并允许在
正则表达式中使用单行注释来提高可阅读性。不知道最终是否会被采纳呢。
当
正则表达式的内容需要改变,或需要从用户输入获取等的时候,也可以采用构造器的方式来定义
正则表达式:
- var regex = new RegExp("pattern" [, "flags"]);
var regex = new RegExp("pattern" [, "flags"]);
其中代表修饰符的第二个参数是可选的。注意到表示要匹配的模式的第一个参数是用双引号而不是斜杠括起来的。
EXAMPLE 1 这个例子中用到了一个包含了许多特性的
正则表达式,与一个字符串进行匹配。
这些特性包括锚,字符组,预定义字符组,量词,捕获型括号,反向引用,环视等。
使用<.*>来匹配标签的话挺容易出问题的,所以这里采用了<[^>]*>来匹配。
EXAMPLE 2 这个例子主要展示了exec()方法的使用。
值得注意的是,
JavaScript中只支持顺序环视(lookahead)而不支持逆序环视(lookbehind),所以当需要逆向环视的时候应当想办法将其转换为正向环视。
一段否定正向环视的代码:
- js> regex = /\b(?!FX)\b/ig
- /\b(?!FX)\b/gi
- js> "Fx's not FX".replace(regex, "Alpha")
- FxAlpha'AlphasAlpha AlphanotAlpha FXAlpha
js> regex = /\b(?!FX)\b/ig
/\b(?!FX)\b/gi
js> "Fx's not FX".replace(regex, "Alpha")
FxAlpha'AlphasAlpha AlphanotAlpha FXAlpha
EXAMPLE 3 这个例子用EXAMPLE 1里的字符串与
正则表达式进行匹配(和捕获),并演示了如何在
正则表达式执行过后引用捕获到的匹配。
EXAMPLE 4 这个例子展示了如何使用
正则表达式进行替换。
EXAMPLE 4展示了
JavaScript中量词的“贪婪”特性——它一定会在
整个字符串中找到符合
正则表达式要求的
最长匹配。更准确的说,是遵循"left-most, longest match"的原则。
在这个例子中,虽然"aarbc"、"aAdbc"与"aarbcaAdbc"都能够匹配,但只有"aarbcaAdbc"才是最长的,因而结果是把整个"aarbcaAdbc"作为匹配,交给替换的逻辑去处理。
正则表达式相关方法(包括字符串的几个方法)
参考:
compile 正则表达式的成员方法。compile()方法可以改变一个
正则表达式实例的匹配模式及其修饰符。它的参数与RegExp构造器一样。
- regex.compile("pattern" [, "flags" ]);
regex.compile("pattern" [, "flags" ]);
exec 正则表达式的成员方法。exec()的作用某种意义上与perl中的=~运算符相同,都是将一个
正则表达式应用在一个字符串上。但是
JavaScript中只能定义match功能的
正则表达式,所以exec也只能匹配而不能替换。
若存在匹配,调用该方法会更新全局的RegExp对象的属性。
perl:
- $str =~ m/PATTERN/;
- # - or -
- $str =~ s/PATTERN/REPLACEMENT/;
- # - and the like -
$str =~ m/PATTERN/;
# - or -
$str =~ s/PATTERN/REPLACEMENT/;
# - and the like -
JavaScript:
- var result = regex.exec(str);
var result = regex.exec(str);
调用exec()会返回一个数组,其中包含有下列属性:
input:用于匹配的输入字符串。
index:开始匹配的位置的索引。
lastIndex:最后一次匹配后的最后位置的索引。
0:最后一次匹配的整个字符串内容。
1 ... n:捕获型括号匹配到的字符串内容。
参照上面的EXAMPLE 2的使用方法。
test 正则表达式的成员方法。test()与perl中的=~运算符也可以说相似,用在判断是否存在匹配时。
调用test()会返回一个boolean值来表明是否存在匹配。
调用该方法
不会影响全局的RegExp对象的属性。
- var existMatch = regex.test(str);
var existMatch = regex.test(str);
match 字符串的成员方法。match()与
正则表达式的exec()作用一样,返回值也一样。
若存在匹配,调用该方法会更新全局的RegExp对象的属性。
- var result = str.match(regex);
var result = str.match(regex);
参见上面的EXAMPLE 3的使用方法。
replace 字符串的成员方法。这个方法是
JavaScript中对于perl的s/PATTERN/REPLACEMENT/的对应物。与perl一样,REPLACEMENT里可以有$1、$2...等变量来引用捕获到的匹配。
若存在匹配,调用该方法会更新全局的RegExp对象的属性。
- str.replace(regex, replace);
str.replace(regex, replace);
参见上面的EXAMPLE 4的使用方法。
search 字符串的成员方法。这个方法与
正则表达式的test()方法一样,用于检验是否存在匹配。
调用该方法
不会影响全局的RegExp对象的属性。
- var existMatch = str.search(regex);
var existMatch = str.search(regex);
split 字符串的成员方法。用
正则表达式(或普通字符串)指定分隔符,将原字符串分割为多个子串后以数组形式返回。
若存在匹配,调用该方法会更新全局的RegExp对象的属性。
- document.write("a string".split(/ */).join(", "));
document.write("a string".split(/ */).join(", ")); // a, s, t, r, i, n, g