前言
说起正则表达式( Regular Expression ),很多人都会头疼,记命令都要记得吐血,不过正则表达式的效率真的是高的一比,完全可以从文本中筛选出你想要的任何内容,所以还是得学啊,并且如果没有正则表达式的话, Linux 也不会那么高效。这玩意以前已经学习过一遍了,没有怎么练习加上过去了好久又给全忘了,因此又得重新再学一遍 == ,其实也没有太多东西,但是要经常练习才能熟练。
常用元字符
元字符不是普通的字符,是代表了某种意义的字符,这里就把基本的元字符给记下来 (如果要匹配的东西是元字符的话要用 \
转义)
元字符 | 意义 |
---|---|
\b | 匹配单词的开始或结束( 中间不少于 1 个 \w ) |
\w | 匹配字母、数字、下划线、汉字 |
\s | 匹配任意的空白符( Tab、空格、换行符 ) |
\d | 匹配一个数字 |
. | 匹配除换行符之外的任意字符 |
^ | 匹配字符串的开头 |
$ | 匹配字符串的结尾 |
[] | 匹配 [] 里的内容 |
() | 给括号内表达式分组 |
\B | 匹配不是单词开头或结束的位置 |
\W | 匹配任意不是字⺟,数字,下划线,汉字的字符,相当于 [^\w] |
\S | 匹配任意不是空⽩符的字符 |
\D | 匹配任意⾮数字的字符,相当于 [^\d] |
#这里说的单词并不是英语单词,是不少于一个 \w
的东西,更精确的说法,\b
匹配这样的位置:它的前⼀个字符和后⼀个字符不全是(⼀个是,⼀个不是或不存在) \w
举个例子
\b\w{6}\b
匹配 刚好6个字符的单词
^\d{5,10}$
匹配你在表单里填写的 5 到 10 位的 QQ 号
重复匹配
上面给的例子中就已经给出了重复的概念了,有了重复的概念,正则表达式才更加简洁高效,下面是一些有关重复的限定符
限定符 | 意义 |
---|---|
* | 重复 0 次或更多次 |
+ | 重复 1 次或更多次 |
? | 重复 0 次或 1 次 |
{n} | 重复 n 次 |
{n,} | 重复 n 次或更多次 |
{n, m} | 重复 n 到 m 次 |
字符匹配
前面已经匹配过数字、空白、字母了,如果想自己定义一个集合取匹配呢,这时要用到我们另外一个元字符 [] 了,它匹配的是一个字符
元字符 | 意义 |
---|---|
[0-9] | 匹配数字,相当于 \d |
[.?!] | 匹配 . 或 ? 或 ! |
[a-z0-9A-Z] | 匹配字母、数字、下划线,相当于 \w (如果没有中文的情况下) |
[^0-9] | 匹配除数字外的任意字符,相当于 [^\d] |
注意 [] 里不用加入空格,否则会把空格给匹配,出现元字符也不用转义,因为它们此时代表的就是普通的字符
举个例子
\(?0\d{2}[) -]?\d{8}
就可以匹配 (010)88886666
和 022-22334455
以及 02912345678
这些格式的号码。
逻辑分支条件
有时候我们的正则表达式可能会匹配到我们不想要的数据,比如上面那个例子就会匹配到 ` 010)12345678 ` 以及 (022-87654321
这样格式的数据,要解决这个问题我们可以用分支条件( 也就是逻辑或 |
)写多一点表达式,只要满足其中的一项就成功匹配到。
举个例子
0\d{2}-\d{8}|0\d{3}-\d{7}
这个表达式能匹配两种以连字号分隔的电话号码:
- 三位区号,如 010-12345678
- 四位区号,如 0376-2233445
#分支条件也有短路效应,只要匹配了左边的表达式就不会再去匹配右边的,所以写的时候要注意顺序。
分组
之前已经介绍了单个字符的重复,如果想让多个字符重复的话,我们可以用 ()
将想要重复的字符括起来让它变成一个分组或者子表达式,然后在括号后面就可以像之前那样用重复限定符了。
举个例子
` ((2[0-
4]\d|25[0-5]|[01]?\d\d?).){3}(2[0-4]\d|25[0-5]|[01]?
\d\d?) 用来匹配 ip 地址,其实就是重复了 3 次
2[0-4]\d|25[0-5]|[01]?\d\d?` 表达式,不难分析。
后向引用
前面用的 ()
实现了多个字符的重复,直接紧跟在后面加上限定符就行了,如果我们不想重复匹配多次,而是要在后面引用这次匹配到的内容该怎么办呢,我们可以用后向引用
每次用 ()
进行一次分组时,()
里的内容都会拥有一个分组,从 1 开始一直递增,第 0 个分组是整个正则表达式本身,所以 \1
就表示重复一次第一个分组捕获到的内容。
举个例子
\b(\w+)\b\s+\1\b
可以用来匹配重复的单词,如 so so
,\b(\w+)\b
表示匹配一个单词,(\w+)
就是分组 1 , \s+
表示一个或多个空白符,紧接着就是 \1
,也就是上次匹配到的单词,然后就以这个单词结束。
零宽断言
零宽断言分为后行断言和先行断言,它们是特殊类型的非捕获组 (也就是说匹配的不是自己,是别人),因为只匹配模式,不占字符,所以叫做零宽。当我们在一种特定模式之前或者之后有这种模式时,会优先使用断言(尤其是匹配 HTML 元素时)。
举个例子
我们想获取输入字符串 $4.44 and $10.88
中 $
字符之后的所有数字。我们可以使用这个正则表达式 (?<=\$)[0-9.]*
,什么意思呢,就是说我断言我要匹配的内容 [0-9.]*
前面一定有一个 $
,否则就匹配失败,所以真正要匹配的是后面的内容而不是括号里的内容.
断言模式 | 意义 |
---|---|
(?=exp) | 正向先行断言(positive lookhead),断⾔⾃⾝出现的位置的后⾯能匹配表达式exp |
(?<=exp) | 正向后行断言(positive lookbehind),断⾔⾃⾝出现的位置的前⾯能匹配表达式exp |
(?!exp) | 负向先行断言(negative lookhead), 断⾔此位置的后⾯不能匹配表达式exp |
(?<!exp) | 负向后行断言(negative lookbehind),断⾔此位置的前⾯不能匹配表达式exp |
举个例子
// positive lookhead
`sinM.`.match(/sin(?=M\.)/g); // ["sin"]
`M.sin`.match(/sin(?=M\.)/g); // null
// positive lookbehind
'sinM.'.match(/(?<=M\.)sin/g); // null
'M.sin'.match(/(?<=M\.)sin/g); // ["sin"]
// negative lookhead
`M.sin`.match(/sin(?!M\.)/g); // ["sin"]
`sinM.`.match(/sin(?!M\.)/g); // null
// negative lookbehind
'sinM.'.match(/(?<!M\.)sin/g); // ["sin"]
'M.sin'.match(/(?<!M\.)sin/g); // null
再举个例子
` (?<=<(\w+)>).*(?=<\/\1>) 匹配 不包含属性的简单HTML标签内⾥的内容,好好思考一下,上面这个表达式可以将
<p>RE</p>` 中的 RE 给匹配出来。
贪婪与懒惰匹配
正则表达式跟人一样,都是贪婪的,所以当有可重复的限定符时,正则表达式会匹配最长的那个结果,有时我们不想让他变得那么贪婪,就可以用懒惰匹配,也就是在限定符后面加个 ?
限定符 | 意义 |
---|---|
*? | 重复任意次,但尽可能少重复 |
? | 重复1次或更多次,但尽可能少重复 |
?? | 重复0次或1次,但尽可能少重复 |
{n,m}? | 重复n到m次,但尽可能少重复 |
{n,}? | 重复n次以上,但尽可能少重复 |
处理标记
标记 | 描述 |
---|---|
i | 不区分大小写: 将匹配设置为不区分大小写。 |
g | 全局搜索: 搜索整个输入字符串中的所有匹配。 |
m | 多行匹配: 会匹配输入字符串每一行。 |
"/.at(.)?$/" => The fat
cat sat
on the mat.
// 这样只会匹配 mat
"/.at(.)?$/gm" => The fat
cat sat
on the mat.
// 这样会匹配 fat sat mat