今天在 segmentfault
回答了一个关于正则表达式的问题,后来细想下,觉得自己对正则表达式的知识其实也不是很熟悉,很多时候都是要用的时候查查找找搞定的,因此我决定把一些关于正则表达式的知识系统地学习和总结下。
说明:为了书写方便,本文中 =>
表示匹配,!=>
表示不匹配。
.
作为一个通配符可以代替除换行符(\n)
外的任一一个字符。
'.ig' => 'big' 和 'pig'
[]
包含在方括号中的内容组成一个字符集合,但是只能匹配其中的一个字符。
'[a-z]b' => 'sb', '[a-z]b' !=> '2b'
在 []
内可以使用脱字符号 ^
来指明不匹配某个字符集合。
'[^a-z]b' => '2b', '[^a-z]b' !=> 'sb'
这些字符类都是正则预定义的:
\f
换页符\n
换行符\r
回车符\t
制表符\v
垂直制表符\s
任何空白字符,包括空格、制表符、换页符等。相当于[\f\n\r\t\v]
\S
任何非空白字符。相当于[^\f\r\t\v]
\w
任何单词字符以及下划线。相当于[A-Za-z0-9_]
\W
任何非单词字符以及下划线。相当于[^A-Za-z0-9_]
\d
任何数字字符。相当于[0-9]
\D
任何非数字字符。相当于[^0-9]
*
模式可以重复0次或多次。
'ha*' => 'h', 'ha*' => 'haaaaaa'
+
模式可以重复1次或多次。
'ha+' !=> 'h', 'ha+' => 'haaaaaa'
模式可以重复0次或1次。
'ha?' => 'h', 'ha?' => 'haaaaaa'
()
用来表示子表达式。
'(very)good' => 'verygood' '(very)*good' => 'veryverygood'
{}
可以用来给子表达式计数。
'(very){2}good' !=> 'verygood', '(very){2}good' => 'veryverygood'
还可以给计数设定范围
'(very){1,3}good' => 'verygood', '(very){1,3}good' => 'veryverygood'
需要注意的是,当写成 {n,}
形式表示匹配 n
次或更多次。
^
脱字符号在此的作用与字符集内的作用不同,它表示匹配的内容在被搜索字符串的开始位置。
'^nb' => 'nba', '^nb' !=> 'wnba'
为了与上面的脱字字符使用进行区分,再举个例子:
'^nb' !=> 'wnba', '[^nb]' => 'wnba'
$
与 ^
相反,它表示匹配的内容在被搜索字符串的末尾位置。
'b$' => 'nb', 'b$' !=> 'nba'
\b
有单词分界符,这个容易模糊,举个例子来理解:
'hello\b' => 'hello world', 'hello\b' => 'hello,world', 'hello\b' !=> 'helloworld'
\B
刚好相反,它表示无非单词分界符。
'hello\B' !=> 'hello world', 'hello\B'!=> 'hello,world', 'hello\B' => 'helloworld'
|
表示子表达式的分支,分支内可选匹配。
'(cba)|(nba)' => 'cba', '(cba)|(nba)' => 'nba'
在上面我们用到了很多特殊的字符来表达特定的意思,但是如果刚好就是想要匹配这些特殊字符本身,而并非是这些特殊字符所表达的意义。此时我们只需在字符前加一个反斜杠 \
。
'$ab' !=> '$abc' //这里我们只是希望匹配 $ab,而不是希望匹配出现在末尾的 ab。
所以我们需要添加反斜杠 \
。
'\$ab' => '$abc' //匹配成功
i
不区分大小写(默认是区分的)m
在匹配首内容或者尾内容时候采用多行识别匹配S
将转义回车取消x
忽略正则中的空白A
强制从头开始匹配D
强制$匹配尾部无任何内容 U
禁止贪婪匹配 只跟踪到最近的一个匹配符并结束当正则表达式中包含能接受重复的限定符 *
或 +
时,为了使表达式得到匹配,通常是匹配尽可能多的字符。这被称为贪婪匹配。
'ha.*' => 'haaaaaaaaaaha'//匹配的结果为最长的 haaaaaaaaaa
懒惰匹配,也就是匹配尽可能少的字符。此时只需要在重复限定符后加一个疑问号 。
'ha.*?' => 'ha'//匹配的结果为最少的 ha
如果我们希望达到断言匹配结果后面一定跟着某个子表达式,比如我们希望匹配 singing
中的 sing
,而不是 singer
中的 sing
,因为我们先行断言 sing
后面的子表达式 ing
,那么我们可以使用 (?=ing)
来实现。
'sing(?=ing)' => 'singing', 'sing(?=ing)' !=> 'singer'
如果我们希望达到断言匹配结果前面一定跟着某个子表达式,比如我们希望匹配 nba
中的 ba
,而不是 cba
中的 ba
,因为我们后发断言 ba
后面的子表达式 n
,那么我们可以使用 (?<=n)
或 (?>n)
来实现。
'(?<=n)ba' => 'nba', '(?<=n)ba' !=> 'cba'
与先行断言相反,我们希望匹配结果后面一定不能跟着某个子表达式,比如,我们希望匹配不以 ing
结尾的 sing
。那么我们可以使用 (?!ing)
来实现。
'sing(?!ing)' !=> 'singing', 'sing(?!ing)' => 'singer'
与后发断言相反,我们希望匹配结果前面一定不能跟着某个子表达式,比如,我们希望匹配不以 n
开始的 ba
。那么我们可以使用 (?<!n)
来实现。
'(?<!n)ba' !=> 'nba', '(?<!n)ba' => 'cba'
我们可以给匹配的子表达式进行固化分组,首先我们明确两点点:
组号分配过程是要从左向右扫描两遍的:第一遍只给未命名组分配,第二遍只给命名组分配--因此所有命名组的组号都大于未命名的组号
如我们希望对 i love you
内的 love
进行分组,可对其进行的操作包括:
1.
(love)
//匹配love
,并自动归到未命名分组中
2.(?<NAME>love)
//匹配love
,并归到命名组中
3.(?:love)
//匹配love
,但不对其进行分组操作
举例说明,我们要匹配 lovelove
,那么我们只需要匹配 love
,然后利用它的组名即可。
'(love)\1' => 'lovelove', '(love)\1' !=> 'love' //未命名分组组名从1开始
这里要注意一点,如果我们需要匹配 love love
,中间有单词分隔时,我们需要用到上文中的单词分解符 \b
和 空白符 \s
。
'\b(love)\b\s\1' => 'love love', '\b(love)\b\s\1' !=> 'lovelove'
()
字表达式因为是内存处理所以优先级最高 + {}
重复匹配相关的优先级第二^ $ \b
字符定位相关优先级第三联系客服