正则表达式--regular expression
说到正则表达式,很多前端小伙伴们是望而却步或者浅尝而止,感觉其难以理解。其实正则表达式并没有那么复杂,只要你清晰地知道你想要解决的问题并学会使用正则表达式,那么你就可以轻易地解决这些问题。
用途
正则表达式(简称regex)是一种工具和其他工具一样,他是为了解决某一类专门的问题发明的。
例如:
- 你正在搜索一个文件中的size单词或者想要替换所有的
size
单词,但是不想要替换包括size
的单词比如fontsize
这样的就不替换。 - 你想要检查一个表单中邮箱或者手机号是否是正确的语法格式。
- 你只想搜索某个文件里特定位置的某个单词,比如行首或者行尾。
这些问题其实是我们写程序时候经常能遇到的,我们也可以通过条件处理和字符串操作来解决它们,但是你的解决方案会十分的复杂。
但是,这些问题我们都可以使用一些精心构造的正则表达式语句来解决。
什么是正则表达式
简单来说正则表达式是一些用来匹配和处理文本的字符串。它是文本处理方面功能最强大的工具之一。正则表达式语言用来构造正则表达式,正则表达式用来完成搜索和替换的操作。
以下的例子都是合法的正则表达式:
.ber.www\.tyweb\.top[a-zA-z0-9_.]*<[Hh]1>.* \r\n\r\n\d{3,3}-\d{3,3}-\d{4,4}
匹配单个字符串
1. 匹配纯文本
- 文本 -
hello world!hello web!
- 正则表达式 -
hello
- 结果 -
hello
world!hello
web!
这里使用正则表达式的是纯文本,它将匹配原始文本里面的所有 hello
。
字母大小写的问题:正则表达式是区分大小的,所以hello
不匹配Hello
,不过绝大数的正则表达式的实现也支持不区分大小写的匹配操作,比如JavaScript
中可以使用i
标志来强制执行一次不区分字母大小写的搜索。
2. 匹配任意字符串
- 文本 -
web1web2tyweeweeweb3df1web2
- 正则表达式 -
web.
- 结果 -
web1
web2
tyweeweeweb3
df1web2
在正则表达式中特殊自负床用来给出要搜索的内容,.
可以匹配任何一个单个的字符。当然,在同一个正则表达式中允许出现多个 .
,并且它既可以连续出现,也可以间隔这出现在模式的不同位置。
3. 匹配特殊字符串
刚刚我们有说到 .
的用法,并且一些特殊字符有一些特殊的用法,那么问题来了,如果我们在需要匹配的字符串中有这些字符串,而我们有想要匹配出来怎么办呢。
我们需要想办法高速正则表达式你需要的是 .
本身而不是他在正则表达式中的特殊含义。所以我们在它前面加上一个 \
(反斜杠)来对它进行转义。
- 文本 -
web12web2tyweeweb3df1web.2
- 正则表达式 -
.web\.
- 结果 -
web1
2web2tyweeweb3df1web.
2 匹配一组字符串
1. 匹配多个字符串中的一个和利用字符区间
上面我们有说过如何匹配单个字符串,但是如果现在有一个文件列表。我们只想找出.是包含我们想要字母的文件怎么办?看例子:
- 文本 -
aweb.htmlcweb.htmlty.htmlwee.htmlweb3.html1web.html
- 正则表达式 -
[ac]web\.html
- 结果 -
aweb.html
cweb.html
ty.htmlwee.htmlweb3.html1web.html 可以看到我们这里使用 [ac]
作为开头,这个集合将只匹配字符 a
或 c
。我们可以多处使用[]
来使得我们的表达式更加灵活,当然我们是需要根据需要来做的。
当然我们如果想要匹配a到z的话当然不可能是写那么多字母模式[a~z]
是完全等价的,相同道理的还有[0~9]
。
2. 取非匹配
字符集合通常用来指定一组必须匹配其中之一的字符。但是某些场合,我们需要反过来做,给出一组不需要得到的字符,换句话说,除了那个字符集合里的字符,其他字符都可以匹配。
- 文本 -
aweb.htmlcweb.htmlty.htmlwee.htmlweb3.html1web.html
- 正则表达式 -
[^0~9]web\.html
- 结果 -
aweb.html
cweb.html
ty.htmlwee.htmlweb3.html1web.html 3. 使用元字符串
元字符串是一些在正则表达式里有特殊含义的字符,类似 .
, ]
所以这些字符就无法用来表示自身,当然之前我们也说过我们在元字符串的前面加上一个反斜杠\
来进行转义,让其匹配自身。
arr[0]
。 - 代码 -
var arr = new Array();console.log(arr[0].length);
- 正则表达式 -
arr\[0\]
当然这个例子有点小题大做,我们平时情况下遇到同时匹配arr[1]
arr[2]
arr[3]
等情况才会用到正则。
4. 匹配空白字符
元字符 | 说明 |
---|---|
[b] | 回退(并删除)一个字符(Backspace键) |
f | 换页符 |
n | 换行符 |
r | 回车符 |
t | 制符表(Tab按键) |
v | 垂直制表符 |
一般情况来说我们匹配 \r
\n
\t
的情况稍微多见一些。
5. 匹配特定的字符类别
元字符 | 说明 |
---|---|
d | 任何一个数字字符(等价于 [0~9] ) |
D | 任何一个非数字字符(等价于 [^0~9] ) |
w | 任何一个字母数字字符(大小写均可)或下划线字符 (等价于[a-zA-Z0-9_] ) |
W | 任何一个非字母数字字符或非下划线字符(等价于[^a-zA-Z0-9_] ) |
s | 任何一个空白字符(等价于[\f\n\r\t\v] ) |
S | 任何一个非空白字符(等价于[^\f\n\r\t\v] ) |
6. 匹配十六或者八进制数值
这里就不详细介绍,我们需要知道的hi正则能做到这一点类似匹配十六进制 \x0A
其实和等价于 \n
,匹配八进制 \011
等价于 \t
。
7. 使用POSIX字符
首先:JavaScrip是不支持在正则表达式中使用POSIX字符的。
稍作了解,POSIX
字符类是一种简写的形式
字符类 | 说明 |
---|---|
[:alnum:] | 任何一个字母或者数字(等价于 [a-zA-Z0-9] ) |
[:alpha:] | 任何一个字母(等价于 [a-zA-Z] ) |
[:blank:] | 空格或者制表符 (等价于 [\t ] ) |
[:cntrl:] | ASCII控制字符(ASCII0到31加上127) |
[:digit:] | 任何一个数字(等价于 [0-9] ) |
[:graph:] | 和 [:print:] 一样但是不包括空格 |
[:lower:] | 任何一个小写字母(等价于 [a-z] ) |
[:print:] | 任何一个可打印字符 |
[:punct:] | 不属于 [:alnum:] 和 [:cntrl:] 的任何一个字符 |
[:space:] | 任何一个空白字符(等价于 [^\f\n\r\t\v ] ) |
[:upper:] | 任何一个小写字母(等价于 [A-Z] ) |
[:xdigit:] | 任何一个十六进制数字(等价于 [a-fA-F0-9] ) |
重复匹配
从之前所了解的匹配规则中我们学会了使用各种元字符,字符集,字符类去匹配单个字符。但是当我们想要匹配出一个类似 loulan@qq.com
这样的邮箱呢?
1. 匹配一个或者多个字符
- 文本 -
hello everyone, you can email me to loulan@qq.com or loulou@qq.com.
- 正则表达式 -
\w+@\w+\.\w+
- 结果 -
hello everyone, you can email me to loulan@qq.com or loulou@qq.com.
想要匹配同一个字符或者字符集的多次重复(不包括0次),只要简单的给这个字符或者字符集加上一个 +
字符作为后缀即可。比如 [0-9]
匹配一个数字, [0-9]+
匹配一个或者多个连续的数字。
需要注意的是给字符集加上+
后缀的时候必须放在字符集[]
的外面。不然就是匹配或者+
的单个字符了。
其实我们刚刚的这个表达式如果遇到 ty.top.@qq.com
就会出现问题。仔细的你会在下面找到答案。
2. 匹配零个或者多个字符
先看一个实例,这段的匹配出现了小问题。 .loulan@qq.com
带上了我们不需要的 .
。
- 文本 -
hello everyone, you can email me to ty.top@qq.com or loulou@qq.com.
- 正则表达式 -
[\w.]+@[\w.]+\.\w+
- 结果 -
hello everyone, you can email me to ty.top@qq.com or .loulou@qq.com.
其实这种匹配模式需要使用*
来进行匹配他的用法和+
一样,但是它能匹配该字符或者字符集零次或者多次出现的情况。
- 文本 -
hello everyone, you can email me to ty.top@qq.com or loulou@qq.com.
- 正则表达式 -
\w+[\w.]*@[\w.]+\.\w+
- 结果 -
hello everyone, you can email me to ty.top@qq.com
or .loulou@qq.com
.
可以稍微理解下这个表达式每个字符所代表的意义,以及为什么能解决我们的问题。
3. 匹配零个或者一个字符
有了一个多个,零个多个,怎么会少了零个或者一个字符的匹配呢。用法和+ *
一样,只不过规则不一样了。我们看下区别:
- 文本 -
http://www.baidu.com/https://www.baidu.com/
- 正则表达式 -
http://[\w./]+
- 结果 -
- 文本 -
http://www.baidu.com/https://www.baidu.com/
- 正则表达式 -
https?://[\w./]+
- 结果 -
有些同学会发现其实在字符集中有些元字符并没有进行转义,但是匹配还是成功了。一般来说在字符集中间的的元字符会被解释成普通字符,不需要转义,当然,转义了的话也没有任何坏处,也还是能匹配成功的。
4. 防止过度匹配
我们看一个问题:
- 文本 -
left middle right
- 正则表达式 -
<[iI]>.*
- 结果 -
<i>left</i> middle <i>right</i>
我们分析一下这个问题,其实是因为.
其实匹配了中间部分的所有字符而没有适可而止将他们分开进行匹配。那么我们怎么让它适可而止呢?
- 文本 -
left middle right
- 正则表达式 -
<[iI]>.*?
- 结果 -
<i>left</i> middle <i>right</i>
很简单的我们在 *
后面加了 ?
就可以让它不再贪婪,我们把加上 ?
的 *
叫做他的惰性版本。
其实刚刚说的这些都是正则匹配里的数字元量符号,一样的使用方法有以下数字元量符:
数字元量符 | 说明 |
---|---|
* | 匹配前一个字符或者字符集的零次或者多次出现 |
+ | 匹配前一个字符或者字符集的一次或者多次出现 |
? | 匹配前一个字符或者字符集的一次或者零次出现 |
{n} | 匹配前一个字符或者字符集的n次重复 |
{m,n} | 匹配前一个字符或者字符集的至少m次至多n次的重复 |
{n, } | 匹配一个字符或者字符集的n次或者更多次重复 |
*? | * 的惰性版本 |
+? | + 的惰性版本 |
{n, }? | {n, } 的惰性版本 |
位置匹配
我们遇到这么一个问题,我们只想匹配 arry
这个单个单词,但是它匹配到了我们不想要的内容:
- 文本 -
arry in tyarryIt.com
- 正则表达式 -
arry
- 结果 -
arry in tyarryIt.com
1. 单词边界
我们需要一个规则来限制边界,\b可以做到
,他限制了一个单词的开始或者结尾
- 文本 -
arry in tyarryIt.com
- 正则表达式 -
\barry\b
- 结果 -
arry in tyarryIt.com
这里我们只匹配 arry
本身,所以我们在前后都加入 \b
。当然我们可以只在开头或者结尾使用来找到以我们想要单词作为开头或者结尾的单词。
需要注意的是\b
只匹配位置不匹配字符。有些正则还支持另外两种元字符\<
\>
只匹配单词的开头和结束,不过支持他们的正则表达式引擎并不多。
与之相反\B
表明不匹配一个单词边界。
2. 字符串边界
用来定义字符串边界的元字符有两个,一个是用来定义字符串开头的 ^
另一个是匹配字符串结尾的 $
。其使用方法是和单词边界一致的。
位置元字符 | 说明 |
---|---|
^ | 匹配字符串的开头 |
\A | 匹配字符串的开头 |
$ | 匹配字符串的结束 |
\Z | 匹配字符串的结束 |
\< | 匹配单词开头 |
\> | 匹配单词结束 |
\b | 匹配单词边界 |
\B | 和\b 相反 |
使用子表达式
子表达式
让我们思考一下下面这个正则:
- 文本 -
arry innbsp;nbsp;tyarryIt.com
- 正则表达式 -
nbsp;{2, }
从表达式中我们能知道写这个表达式的本意是想匹配连续的 nbsp;
。但是这个表达式并不能达到我们预期的结果,因为 {2, }
只会匹配其前面紧挨的字符。所以只能匹配nbsp;;
这样的字符串,但是无法匹配 nbsp;nbsp;
这样的字符串。
上面这个表达式就引出了子表达式的概念,子表达式是一个更强大的表达式的一部分,把表达式划分成一系列的表达式是为了把那些子表达式当作一个独立的元素来使用。子表达式必须使用 ()
来括起来。
这样再让我们完美解决上面的需求:
- 文本 -
arry innbsp;nbsp;tyarryIt.com
- 正则表达式 -
(nbsp;){2, }
这样的话 (nbsp;)
是一个表达式。它将被视为一个独立的元素,而他后面的 {2, }
将作用域这个子表达式。
子表达式的嵌套
子表达式允许嵌套,并且允许多重的嵌套,这种嵌套,在层次上理论没有限制,但是在我们的工作中当然是需要适可而止的。太多的嵌套会让匹配模式变得难以阅读和理解。
回溯引用-前后一致匹配
前后一致的问题让我们不禁想到HTML的标签:
- 文本 -
it is h1
it is h2
it is h3
it is h4
- 正则表达式 -
<[hH][1-6]>.*?
这可能是我们在想到匹配标签时候心里的第一想法。
但是,细心的同学会发现我们最后的一个标签 <h4>***</h5>
因为手误写错了。但是思考一下我们发现,这种错误的地方也会被成功匹配。那我们怎样才能前后一致进行匹配呢?
回溯引用匹配
下面这个表达式把字符串中重复的几个单词匹配出来了。
- 文本 -
Try you you best and and you will win win the game.
- 正则表达式 -
[ ]+(\w+)[ ]+\1
- 结果 -
Try you you best and and you will win win the game.
我们来分析一下上面的表达式,[ ]+
匹配一个或者多个空格,\w+
匹配一个或者多个字母或者数字,随后,[ ]+
匹配一个或者多个空格,而 (\w+)
又是一个子表达式,当然这里子表达式不是用来做重复匹配的,这里只是把这部分表达式单独划分出来以便在后面进行引用。最后一部分 \1
这就是一个回溯引用,而它引用的是前面划分出来的那个子表达式,他的意思是当前面的 \w+
匹配到 you
的时候他也匹配 you
,前面的\w+
匹配到 and
的时候他也匹配 and
。当然如果有多个子表达式 \1 \2 \3
分别代表模式里的第一,第二,第三个表达式,我们可以把回溯引用想象成一个变量的重复调用。
前后查找:
往前查找
- 文本 -
https://www.tyweb.top/http://www.tyweb.top/ftp://ftp.tyweb.top/
- 正则表达式 -
.+(?=:)
- 结果 -
https://www.tyweb.top/
http://www.tyweb.top/
ftp://ftp.tyweb.top/
在上面的正则表达式中 .+
匹配任意文本,子表达式 ?=:
匹配 :
,但是需要注意的是 :
并没有出现在最终的结果中,我们用?=:
表明的是,只要找到 :
就可以了,不要包括在最终的匹配结果里。用术语来说就是“不消费”它。
往后查找
?<=
用来做向后查找,用法和向前查找大同小异。
往前往后查找相结合
我们直接匹配标签中间的文本:
- 文本 -
<b>hello</b>
- 正则表达式 -
(?<=<[bB]>).*(?= )
嵌入条件
正则表达式里的条件要用 ?
来定义。事实上,你们一家见过几种非常特定的条件了。
-
?
匹配前一个字符或者表达式 -
/=
和?<=
匹配前面或者后面的文本,如果它存在的话。
嵌入条件语法也使用了 ?
,因为嵌入条件不外乎下面两种情况
- 根据一个回溯引用来进行条件处理
- 根据一个前后查找来进行条件处理
元字符 | 说明 |
---|---|
() | 定义一个子表达式 |
n | 匹配第n个子表达式 |
?= | 向后查找 |
?<= | 向前查找 |
?! | 负向前查找 |
?<! | 负向后查找 |
?() | 条件(if then) |
?()| : 条件(if then else)