正则表达式用法总结

@fwon 2014-07-09 15:25:24发表于 fwon/blog

正则表达式用法总结


正则表达式的字符类

字符 匹配
[...] 方括号内的任意字符
[^...] 不在方括号内的任意字符
. 除换行符和其他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] 退格直接量

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

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

非贪婪的重复

我们同样可以使用正则表达式进行非贪婪匹配。只须在待匹配的字符后跟随一个问号即可:"??"、"+?"、"*?" 或"{1,5}?"。
正则表达式/a+/可以匹配一个或多个连续的字母a。当使用"aaa"作为匹配字符串时,正则表达式会匹配它的三个字符。但是/a+?/也可以匹配一个或多个连续字母a,但它是尽可能少地匹配。我们同样将"aaa"作为匹配字符串,但后一个模式只能匹配第一个a。
但当使用"aaab"作为匹配字符串时,它会匹配整个字符串,和该模式的贪婪匹配一模一样。这是因为正则表达式的模式匹配总是会寻找字符串中第一个可能匹配的位置。由于该匹配是从字符串的第一个字符开始的,因此在这里不考虑它的子串中更短的匹配。

选择、分组和引用

字符"|"用于分隔供选择的字符。例如/ab|cd|ef/
选择项的尝试匹配次序是从左到右,直到发现了匹配项。如果左边的选择项匹配。就忽略右边的匹配项,即时它产生更好的匹配。因此,当正则表达式/a|ab/匹配字符串"ab"时,它只能匹配第一个字符。

正则表达式中的圆括号作用

  • 把单独的项组合成子表达式,例如/java(script)?/可以匹配字符串"java",其后可以有"script"也可以没有。/(ab|cd)+|ef/可以匹配字符串"ef",也可以匹配字符串"ab"或"cd"的一次或多次重复。
  • 在完整的模式中定义子模式。当一个正则表达式成功地和目标字符串相匹配时,可以从目标串中抽出和圆括号中的子模式相匹配的部分。
  • 允许在同一正则表达式的后部引用前面的子表达式。这是通过在字符""后加一位或多位数字来实现的。这个数字指定了带圆括号的子表达式在正则表达式中的位置。例如,\1引用的是第一个带圆括号的子表达式,\3引用的是第三个带圆括号的子表达式。
    注意:因为子表达式可以嵌套另一个子表达式,所以它的位置是参与计数的左括号的位置。
    例如,在下面的正则表达式中,嵌套的子表达式([Ss]cript)可以用\2来指代: /([Jj]ava([Ss]cript)?)\sis\s(fun\w*)/
    对正则表达式中前一个子表达式的引用,并不是指对子表达式模式的引用,而指的是与那个模式相匹配的文本的引用。
    如果要匹配左侧和右侧的引号,可以使用如下的引用: /(['"])[^'"]*\1/
    \1匹配的是第一个带圆括号的子表达式所匹配的模式。在这个例子中,存在这样一条约束,那就是左侧的引号必须和右侧的引号相匹配。

在正则表达式中不用创建带数字编码的引用,也可以对子表达式进行分组。它不是以"("和")"进行分组,而是以"(?:"和")"来进行分组,比如,考虑下面这个模式:
/([Jj]ava(?:[Ss]cript)?)\sis\s(fun\w*)/
这里,子表达式(?:[Ss]cript)仅仅用于分组,因此复制符号"?"可以应用到各个分组。这种改进的圆括号并不生成引用,所以在这个正则表达式中,\2引用了与(func\w*)匹配的文本。

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

字符 匹配
(...) 组合
(?:...) 只组合,把项组合到一个单元,但不记忆与该项相匹配的字符
\n 和第n个分组第一次匹配的字符相匹配

指定匹配位置

我们使用单词的边界\b来代替真正的空格符\s进行匹配(或定位)。这样正则表达式就写成了/\bJava\b/。元素\B将把匹配的锚点定位在不是单词边界之处。因此,正则表达式/B[Ss]cript/与"JavaScript"和"postscript"匹配,但不与"script"和"Scripting"匹配。
零宽正向先行断言:"(?="和")"之间加入一个表达式,说明括号内的表达式必须正确匹配,但不是真正意义上的匹配。可以使用/[Jj]ava([Ss]cript)?(?=:)/。这个正则表达式可以匹配"JavaScript:The Definitive Guide"中的"JavaScript",但是不能匹配"Java in a Nutshell"中的"Java",因为他后面没有冒号。
零宽负向先行断言:"(?!"和")"中间插入一个表达式,说明括号内的表达式
不允许匹配

正则表达式中的锚字符

字符 匹配
^ 字符串开头或一行的开头
$ 字符串结尾或一行结尾
\b 匹配一个单词的边界
\B 匹配非单词边界的位置
(?=p) 零宽正向先行断言,要求接下来的字符都与p匹配,但不能包括p的那些字符
(?!p) 零宽负向先行断言,要求接下来的字符不与p匹配,只能用在匹配符的后面

正则表达式修饰符

字符 匹配
i 不区分大小写
g 全局匹配
m 多行匹配模式

例子1

function replace(content){
  var reg = '\\[(\\w+)\\]',
    pattern = new RegExp(reg, 'g');
  return content.replace(pattern, '<img src="img/$1.png">');
}
//
function replace(content){
  return content.replace(/\[(\w+)\/g, '<img src="img/$1.png">');
}

例子2

//zero-width look behind的替换方案
//(?<=...)和(?<!...)
//方法一:反转字符串,用lookahead进行搜索,替换以后再倒回来,例如:
String.prototype.reverse = function () {
    return this.split('').reverse().join('');
}
//模拟'foo.bar|baz'.replace(/(?<=\.)b/, 'c') 即将前面有'.'的b换成c
'foo.bar|baz'.reverse().replace(/b(?=\.)/g, 'c').reverse() //foo.car|baz

//方法二:不用零宽断言,自己判断
//模拟'foo.bar|baz'.replace(/(?<=\.)b/, 'c') 即将前面有'.'的b换成c
'foo.bar|baz'.replace(/(\.)?b/, function ($0, $1) {
    return $1 ? $1 + 'c' : $0;    
}) //foo.car|baz
//模拟'foo.bar|baz'.replace(/(?<!\.)b/, 'c') 即将前面没有'.'的b换成c
'foo.bar|baz'.replace(/(\.)?b/, function ($0, $1) {
    return $1 ? $0 : 'c';    
}) //foo.bar|caz
//这个方法在一些比较简单的场景下有用,并且可以和lookahead一起用
//但也有很多场景无效,例如:
//'tttt'.replace(/(?<=t)t/g, 'x') 结果应该是'txxx'
'tttt'.replace(/(t)?t/g, function ($0, $1) {
    return $1 ? $1 + 'x' : $0;
}) // txtx

例子3

$&符号的使用

function escapeRegExp(str) {
  return str.replace(/[abc]/g, "($&)");
}

var str = 'a12b34c';
console.log(escapeRegExp(str)); //(a)12(b)34(c)

例子4

贪婪与非贪婪,加上?为非贪婪

var s = '1023000'.match(/(\d+)(0*)/);
s
["1023000", "1023000", ""]

var s = '1023000'.match(/^(\d+)(0*)$/);
s
["1023000", "1023000", ""]

var s = '1023000'.match(/^(\d+?)(0*)$/);
s
["1023000", "1023", "000"]

var s = '1023000'.match(/(\d+?)(0*)/);
s
["10", "1", "0"]