在处理字符串的时候,经常会有查找某些符合规则的字符串的需要,正则表达式就是用于描述这些规则的工具。
在 windows/Dos 下有用于文件查找的 通配符,* 和 ? 。例如:
- *.doc 表示查找目录下所有的 word 文档
和通配符类似,正则表达式也是用来进行文本匹配的工具,只不过比起通配符它能更精确地描述你的需求。
通过实例入门
1 | var reg = /\bhi\b/ig; |
通过该实例最终的打印结果可以看见,这里仅仅将最后两个值 HI 和 Hi 输出了,那么这里的正则表达式究竟做了一些什么呢?
首先,看一下正则表达式 /\bhi\b/
中的 \b
,\b
是正则表达式规定的一个特殊代码,也叫元字符(metacharacter),代表着单词的开头或结尾,也就是单词的分界处,虽然英文单词是由空格、标点符号、换行来进行分隔,但 \b
并不匹配这些单词分隔字符中的任何一个,它只匹配一个位置。
那么上面这个例子中就是将 hi 这个单词匹配出来,并且不区分大小写,虽然有 history 和 ihslsaaahI 这两个任意单词,但是它们并不满足正则表达式中查找出一个单独的单词 hi(即当前单词 hi 的前后都有分隔符进行分隔的单个单词) 的条件;这里 \b
学了个明白。
再看一个实例:
1 | var reg = /\bhi\b.*\bLucy\b/ig; |
最终得到的结果如上实例打印值,这里为什么会是出现的这么长一串字符串值呢?
通过第一个实例可以知道 \b
的作用,那么这里出现这种结果的条件肯定是正则表达式中间的 .*
这个操作,这里究竟是在干什么?
首先,这里的 .
也是一个元字符,它的含义是匹配除了换行符以外的任意字符。 *
同样是元字符,不过它代表的不是字符,也不是位置,而是数量,它的作用是指定 *
前面的内容可以连续重复使用任意次以使整个表达式得到匹配。
因此,这就明白了,.*
连在一起就意味着任意数量的不包含换行符的字符;所以得到了示例中的一串字符。
元字符
在上面的实例中了解到了 \b
、.
、*
这几个元字符,而正则表达式中的元字符并不仅仅只有这几个,比如还有 \s
、\w
、\d
等。
最后,常用的元字符如下:
- . 匹配除换行符以外的任意字符
- \s 匹配任意的空白符,包括空格、制表符(Tab)、换行符、中文全角空格等
- \w 匹配字母、数字、下划线、汉字
- \d 匹配数字
- \b 匹配单词的开始或结束位置
- ^ 匹配字符串的开始
- $ 匹配字符串的结束
这里总结了常用的元字符,如果要查找的内容是元字符本身的话该怎么办呢?
例如要查找的是 .
号或者 *
号,这如果直接在正则表达式中的话肯定回出问题,它们会被解释成元字符该有的意思。这个时候就得使用 \
来取消这些字符的特殊意义。因此,这里使用 \.
或 \*
来查找元字符本身,当然,如果要查找 \
本身的话也得用 \\
来表示。
这里对常用的元字符进行了统计,那么,有时候需要查找的字符不属于某个能简单定义的字符类,比如想查找出了数字以外,其它任意字符都行的情况,这是是不是需要一个反义?那么常用的反义代码有哪些:
常用的反义代码:
- \S 匹配任意不是空白符的字符
- \W 匹配任意不是字母、数字、下划线、汉字的字符
- \D 匹配任意非数字的字符
- \B 匹配不是单词开头或结束的位置
- [^x] 匹配除了x以外的任意字符
- [^aeiou] 匹配除了 aeiou 这几个字母以外的任意字符
1 | var regStr = '/\S+/'; |
重复
在查找一个符号或者字符并不总是只会有一个,那么匹配重复的方式改怎么做?
常用的限定符如下:
- * 重复零次或更多次(星号)
- + 重复一次或更多次(加号)
- ? 重复零次或一次
- {n} 重复 n 次
- {n,} 重复 n 次或更多次
- {n,m} 重复 n 次到 m 次
字符类
如果想要查找数字、字母、数字或者空白很简单,前面的元字符就可以对它们进行操作,但是如果想匹配没有预定义元字符的字符集合(如:a、e、i、o、u等)该怎么办?
很简单,直接把它们放在方括号里面就行,如 [aeiou]
就匹配任何一个英文元音字母,[.?!]
匹配标点符号 .
、?
或!
号。
我们还可以指定一个字符范围,像 [0-9]
代表的是与 \d
一致的意思,只是 \d
只匹配一位数字。
看一个复杂的表达式:/\(?0\d{2}[) -]?\d{8}/
这个表达式可以匹配几种格式的电话号码,如:(023)88889999、023-88889999、02388889999。
分枝条件
在字符类学习内容里面有一个复杂的正则表达式:/\(?0\d{2}[) -]?\d{8}/
;如下:
1 | var numStr = '023)88889999'; |
这里将这种不规范的号码格式也能匹配成功,那么要解决这个问题,我们需要用到分枝条件。
正则表达式里的分枝条件指的是有几种规则,如果满足其中任意一种规则都应该当成匹配成功,具体的方法就是用 |
把不同的规则分开;如下:
1 | var regStr = '/0\d{2}-\d{8}|0\d{3}-\d{7}/'; |
注意:使用分枝条件时,其各个条件的顺序很重要,顺序不同,匹配的条件结果也不同,因为在匹配分枝条件时,将会从左到右的测试每个条件,如果满足了条件就不会去管其他条件了。
分组
前面学习过程中已经知道了怎么对单个重复的字符进行匹配(直接在字符后面加上限定符就行),但是如果想要重复多个字符怎么办呢?答案就是使用小括号来指定子表达式(分组);然后就可以指定子表达式的重复次数了。
1 | var regStr = '/(\d{1,3}\.){3}\d{1,3}/'; |
后向引用
使用小括号指定一个子表达式后,匹配这个子表示的文本(也就是此分组捕获的内容)可以在表达式或其他程序中作进一步处理;默认情况下,每个分组会自动拥有一个组号,规则是:从左向右,以分组的左括号为标志,第一个出现的分组的组号为1,第二个为2,以此类推。
后向引用用于重复搜索前面某个分组匹配的文本;例如匹配重复的单词:
1 | var reg = /\b(\w+)\b\s+\1\b/; |
上面的表达式能够匹配重复的单词,那么表达式中的 \1
是怎么工作的呢?它代表的是什么?答案很明显,\1
代表分组1匹配的文本,及前面的那个小括号括起来的内容的分组号是1。
除此之外,还可以自己指定子表达式的组名,要指定一个子表达式的组名,可以使用语法:(?<word>\w+)
或者把尖括号换成 '
也行,如:(?'word'\w+)
,这样就把 \w+
的组名指定为 word
了;如果要反向引用这个分组捕获的内容,可以使用 \k<word>
,所以前面的例子也可以写成如下:
1 | var reg = /\b(?<word>\w+)\b\s+\k<word>\b/; |
常见的分组语法统计:
- 捕获
- (exp) 匹配 exp,并捕获文本到自动命名的组里
- (?
exp) 匹配 exp,并捕获文本到名称为 name 的组里,也可以写成 (?’name’exp) - (?:exp) 匹配 exp,不捕获匹配的文本,也不给此分组分配组号
- 零宽断言
- (?=exp) 匹配 exp 前面的位置
- (?<=exp) 匹配 exp 后面的位置
- (?!exp) 匹配后面跟的不是 exp 的位置
- (?<!exp) 匹配前面不是 exp 的位置
- 注释
- (?#comment) 这种类型的分组不对正则表达式的处理产生任何影响,用于提供注释
这里学习了捕获类里面的前两种语法,第三个 (?:exp)
不会改变正则表达式的处理方式,只是这样的组匹配的内容不会像前两种那样被捕获到某个组里,也不会拥有组号。
零宽断言
在查找某些内容(但并不包含这些内容)之前或之后的东西,也就是说它们像 \b
^
$
那样用于指定一个位置,这个位置应该满足一定的条件(即断言),因此它们被称为零宽断言。
(?=exp)
也叫 零宽度正预测先行断言,它断言自身出现的位置的后面能匹配表达式 exp,例如:
1 | var reg = /\b\w+(?=ing)\b/ig; |
这里匹配出了 exp 整个单词前面的部分内容。
(?<=exp)
也叫 零宽正回顾后发断言,它断言自身出现的位置的前面能匹配表达式 exp,例如:
1 | var reg = /(?<=sin)\w+\b/ig; |
这里最终匹配出了 exp 整个单词后面的部分内容。
负向零宽断言
前面提到过怎么查找不是某个字符或不在某个字符里的字符的方法(反义)。但是如果我们只是想要确保某个字符没有出现,但并不想去匹配它时怎么办?
例如:我们想查找一个单词它里面出现了字母q,但是q后面跟的不是字母u,我们可以怎么做?
1 | var reg = /\b\w*q[^u]\w*\b/ig; |
这里得到了最终的结果,但是 aquery 是被排除没有匹配的。但是这里有一个特殊的地方 qq and
被一起匹配出来了,这是因为 [^u]
总要匹配一个字符,所以如果q是单词的最后一个字符的话,后面的 [^u]
将会匹配q后面的单词分隔符或者其他的字符,而后面的 \w*\b
会匹配下一个单词,于是就出现了这个 qq and
被一起匹配出来了;那怎么样能解决这个问题呢?
负向零宽断言了解一下,因为它只匹配一个位置,不消费任何字符,如下:
1 | var reg1 = /\b\w*q(?!u)\w*\b/ig; |
零宽度负预测先行断言 (?!exp)
,断言此位置的后面不能匹配表达式 exp;例如 \d{3}(?!\d)
匹配三位数字,而且这三位数字的后面不能是数字。
同理,零宽度负回顾后发断言 (?<!exp)
,断言此位置的前面不能匹配表达式 exp;例如 (?<![a-z])\d{7}
匹配前面不是小写字母的7位数字。
注释
小括号的另一种用途是通过语法 (?#comment)
来包含注释。例如:2[0-4]\d(?#200-249)|25[0-5](?#250-255)|[01]?\d\d?(?#0-199)
;其中对各个匹配数据做了注释信息。
贪婪与懒惰
当正则表达式中包含能接受重复的限定符时,通常的行为是匹配尽可能多的字符。以 a.*b
为例,它将会匹配最长的以 a 开始,以 b 结束的字符串。如果用来匹配 aabab 的haul,它会匹配整个字符串 aabab,这称为贪婪匹配。
有时候,我们更需要懒惰匹配,也就是匹配尽可能少的字符。将前面的限定符转换成懒惰匹配模式,只要在它后面加上一个问号 ?
即可。即 .*?
就意味着匹配任意数量的重复,但是在能使整个匹配成功的前提下使用最少的重复;例如 a.*?b
匹配最短的以 a 开始,以 b 结束的字符串 aabab,它会匹配 aab 和 ab。
懒惰限定符
- *? 重复任意次,但尽可能少重复
- +? 重复1次或更多次,但尽可能少重复
- ?? 重复0次或1次,但尽可能少重复
- {n,m}? 重复n到m次,但尽可能少重复
- {n,}? 重复n次以上,但尽可能少重复
其它语法
- \a 报警字符
- \b 单词分界位置,如果在字符类里使用代表退格
- \t 制表符 Tab
- \r 回车
- \v 竖向制表符
- \f 换页符
- \n 换行符
- \e Escape
- \0nn ASCII代码中八进制代码为nn的字符
- \xnn ASCII代码中十六进制代码为nn的字符
- \unnnn Unicode代码中十六进制代码为nnnn的字符
- \cN ASCII控制字符,比如 \cC 代表 Ctrl+C
- \A 字符串开头(类似^,但不受处理多行选项的影响)
- \Z 字符串结尾或行尾(不受处理多行选项的影响)
- \z 字符串结尾(类似$,但不受处理多行选项的影响)
- \G 当前搜索开头
- \p{name} Unicode中命名为name的字符类,例如
\p{isGreek}
- (?>exp) 贪婪子表达式
- (?
- exp) 平衡组 - (?im-nsx:exp) 在子表达式exp中改变处理选项
- (?im-nsx) 为表达式后面的部分改变处理选项
- (?(exp)yes|no) 把exp当做零宽正向先行断言,如果在这个位置能匹配,使用yes作为此组的表达式,否则使用no
- (?(exp)yes) 同上,只是使用空表达式作为no
- (?(name)yes|no) 如果命名为name的组捕获到了内容,使用yes作为表达式,否则使用no
- (?(name)yes) 同上,只是使用空表达式作为no
总结
好吧。。内容比较多,自己都已经整晕了。。