正则表达式快速入门

掌握正则让你的效率飞起来

Posted by kevin on April 5, 2019

前言

说起正则表达式( 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)88886666022-22334455 以及 02912345678 这些格式的号码。

逻辑分支条件

有时候我们的正则表达式可能会匹配到我们不想要的数据,比如上面那个例子就会匹配到 ` 010)12345678 ` 以及 (022-87654321 这样格式的数据,要解决这个问题我们可以用分支条件( 也就是逻辑或 | )写多一点表达式,只要满足其中的一项就成功匹配到。

举个例子

0\d{2}-\d{8}|0\d{3}-\d{7} 这个表达式能匹配两种以连字号分隔的电话号码:

  1. 三位区号,如 010-12345678
  2. 四位区号,如 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 

参考文章 1 参考文章 2 参考文章 3