正则表达式语法
从下面开始,将逐步讲解正则表达式的语法
匹配普通文本
普通匹配文本直接输入文本内容即可
文本信息
例如我们匹配python
/python
结果如下:
注意,我们直接进行纯文本匹配的话,就会匹配出来所有的python内容,即便python这个字符串是包含在另一个单词中
例如这里的pythonbrew
和pythonz
以及第33行的带有python的连接
此外,正则表达式是不区分大小写的,所以python
也会匹配到Python
实际上我们只想要单纯的python单词,并不想要python作为其他字符串的子串,在后续的学习中,我们将逐渐学习如何只匹配我们需要的内容
匹配一个任意字符
在正则表达式中.
是一个特殊符号,我们成为元字符,元字符.
可以用来匹配任意的一个字符
例如c.t
可以匹配到cat
,cot
,c t
,c5t
等
例如匹配如下内容
/py.
可以看到,匹配的结果不仅有pyt
,还有pye
我们也可以在一个正则表达式中多次使用.
此外,我们如果直接在一个正则表达式中使用.
,那么将会被视为通配符,而非普通文本.
,如果我们想要匹配.
本身的话,我们可以使用转义符号\
来使.
失去作用,从而作为纯文本被匹配
实际上\
可以使任何的元字符失去作用成为一个纯文本来匹配,例如
/..\.
\
本身也是一个元字符,我们也可以转义\
本身来匹配\
,例如
/...\\..
匹配指定字符集和中的一个字符
前面使用.
能够匹配一个任意的字符,但是有的时候我们却希望匹配一个在指定范围的字符
例如我们只想在匹配一个数字,而非任意的字符,因为任意的字符将会匹配到符号,字母等等
我们可以使用元字符[ ]
来匹配一个指定字符集合中的一个,两个中括号见间的内容将作为匹配项,并且其中的部分元字符会被当做普通文本处理
例如匹配
/..[0123456789]..
注意[0123456789]
只会匹配一个数字
实际上我们可以使用-
连字符来连接两个字符,这样将会匹配的范围为前后两个字符的ASCII码中的所有字符
而且在一对[]
内可以多次使用简写形式以及与单个字符混合使用,例如
/..[0-9.][a-z+_0-9]\.
匹配非指定字符集和中的一个字符
实际上我们除了指定匹配的字符集合,我们也可以进行反向匹配,即匹配不是指定字符集合中的一个字符
例如匹配除了数字和字母以外的所有字符
我们可以将元字符[^ ]
来指定不匹配的字符集合,例如
/[^0-9a-bA-B]
匹配空白字符
元字符大致可以分为两种:
- 用于匹配文本的,例如
.
- 作为正则表达式的一部分,如
[ ]
,[^ ]
空白字符由于不能像普通字母一样来打印出来(虽然具有视觉效果),所以我们使用特元字符来表示空白,并且匹配的时候不会类似于匹配其他文本一样会有高亮显示
常见的换页符如下:
元字符 | 说明 |
---|---|
\f | 换页符 |
\n | 换行符 |
\r | 回车符 |
\t | 制表符(Tab键) |
\v | 垂直制表符 |
例如匹配
/..\n\n
实际上\n\n
会匹配到连续两个换行,但是由于空白没有高亮显示,所以匹配上了空了一行的文本的末尾
实际上如果没有..
,单纯的匹配空白字符,将不会有任何高亮显示(至少在vscode的vim插件中是这样的)
匹配数字 / 非数字
前面我们讲过,可以使用[ ]
或者[^ ]
元字符来匹配或者不匹配指定区间的字符
所以实际上可以用[0-9]
或者[^0-9]
来匹配数字或字母或者数字与非字母
但是我们可以使用\d
元字符来匹配任意一个数字,使用\D
元字符来匹配任意一个非数字
例如
/.\d\D[A-Z]..
匹配数字或字母 / 非数字与字母
和\D
与\d
元字符一样, 我们可以使用\w
元字符来匹配任意一个数字或字母(等价于[a-zA-Z]
),使用\W
来匹配任意一个非数字与字母(等价于[^a-zA-Z]
)
例如
/.\w\W\...
匹配任意的空白字符 / 非空白字符
我们可以使用\s
元字符来匹配任意一个空白字符(等价于[\f\n\r\t\v]
),或者使用\S
元字符匹配任意一个非空白字符(等价于[^\f\n\r\t\v]
)
例如
/.\s[A-Z]
使用POSIX字符类
POSIX是一种也输得标准字符类集,许多正则表达式都支持的一种特殊的字符集合的简写形式
下面列出12个常用的POSIX字符类
字符类 | 说明 |
---|---|
[:alnum:] | 任何一个数组或字母,等价于\w 或[0-9a-zA-Z] |
[:alpha:] | 任何一个字母,等价于[a-zA-Z] |
[:blank:] | 空格或者制表符,等价于[\t ] |
[:cntrl:] | ASCII控制字符(ASCII0-ASCII31,以及ASCII127) |
[:digit:] | 任何一个数字,等价于`[0-9 |
[:print:] | 任何一个可打印的字符 |
[:graph:] | 同[:print:],但是不包含空格 |
[:lower:] | 任何一个小写字母 |
[:upper:] | 任何一个大写字母 |
[:punct:] | 任何一个不是[:alnum:],也不是[:cntrl:]的任意一个字符 |
[:space:] | 任何一个空白字符,包括空格 |
[:xdigit:] | 任何一个十六进制数字,等价于[a-fA-F0-9] |
不过vscode上的vim插件好像不支持使用POSIX字符类,因此这里使用原版的vim,使用:set hlsearch
命令高亮搜索
此外,POSIX类字符必须放在[ ]
中
例如
/[[:alnum:]][[:space:]]
重复匹配一个或多个字符
有的时候我们想要进行重复匹配某个字符或者字符集合,我们可以直接使用+
元字符
但是注意,+
元字符至少会匹配一个字符
例如
/ht+p
匹配零个或多个字符
+
可以重复匹配一个或多个字符,那么如果我们想要重复匹配一个可有可无的字符怎么办?
我们可以使用*
元字符,*
元字符的作用就是其前面的字符可选你
例如
/ht*
这里*
让t
变成可选的匹配,所以我们能看到匹配了h
,H
,ht
,htt
匹配零个或一个字符
*
元字符可以让前一个字符变为可选,同时可以进行多次重复匹配
类似,?
元字符可以让前一个字符变为可选,同时可以进行一次匹配
例如
/versions?
这里能看到,我们匹配到了version
和versions
两个对象
事实上,为了能够使得?
元字符匹配的对象突出,我们通常把要匹配的对象用单独的[ ]
元字符包括起来
/ht+p[s]?
指定重复匹配的次数
前面用来重复匹配的元字符,都只能实现指定次数的匹配,没法实现我们需要的次数的匹配
例如我们需要匹配RGB值的模式是三组六位十六进制数字(如FFFFFF),如果我们使用*的话,将无法高效的匹配到我们需要的字符
为此我们可以使用{ }
元字符来指定匹配的次数
使用的语法如下
待匹配的元字符{最低匹配次数,最高匹配次数}
或
待匹配的元字符{指定的匹配次数}
或
带匹配的元字符{至少匹配的次数,}
例如我们匹配版本号
/\d{1,2}\.\d{1,2}\.\d{1,2}
防止过度匹配
+
与*
元字符都是贪婪型的元字符,所以其匹配行为是越多越好,例如我们要匹配<b>
和</b>
两个标签之间的全部内容
匹配的文本是:
<b> JACK </b> this is a trial. <b> Lucy <\b>
我们使用如下正则表达式匹配
<[Bb]>.*<\/[Bb]>
但是匹配的效果却是
我们发现尽管我们只想要匹配两个<b>
与</b>
之间的内容,但是却把这一句话全部匹配上了
这就是因为+
与*
是贪婪的元字符,他们会尽可能的向后匹配到符合要求的字符串
为此,我们可以使用贪婪型元字符的懒惰型模式,匹配到第一个结果后将停止
贪婪型元字符 | 对应的懒惰型元字符 |
---|---|
* | *? |
+ | +? |
{n,} | {n,}? |
所以我们使用下面的正则表达式来匹配
/<[Bb]>.*?<\/[Bb]>
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-qYa3E0dl-1595774483475)(图片/2020-07-25 18-16-00 的屏幕截图.png)]
匹配边界
我们前面说过,如果我们匹配cat
的话,不仅会匹配到cat
,还会匹配到所有包含cat
的单词.
如果我们只想匹配到单个的cat
单词,那么这个时候就需要实用边界匹配
我们可以是用\b
元字符来匹配单词的边界(实际上匹配的是单词间的分隔),例如
/\bas\b
可以看到即便是bash
这类含有as的单词并没有被选中,仅仅选中了as
单词
匹配字符串边界
在正则表达式介绍中,我们说到一个文档实际上是一整个非常长的字符串,其中的字符包括可视的abc等字母数字,也包括控制格式的控制字符
匹配字符串边界实际上指的就是匹配每一段的的开头或结尾,例如HTML文档中位于行首的<HTML>标签
^
元字符表示字行开头,$
元字符代表行结尾
例如
/^[ ]+echo
表示仅匹配开头具有多个空格以及echo
我们再匹配在行尾的bashrc
/bashrc$
划分子表达式
在一个单一的正则表达式中,每一个元字符会被视作一个实体(entity)
但是有的时候我们却希望几个元字符组合在一起被视为一个实体(entity)
例如我们想要查找一个IP地址.对IP地址分析后发现IP地址连同点可以分为四个部分,每个部分可能有一到三个数字加上一个点
为此我们可以使用如下的正则表达式
/[\d]{1,3}\.[\d]{1,3}\.[\d]{1,3}\.[\d]{1,3}
实际上上面最关键的部分就是[\d]{1,3}\.
,我们把这一部分重复了三次
我们于是希望能够对[\d]{1,3}\.
整体指定重复三次,即{3}
但是{ }
元字符仅会对前面的一个实体进行三次重复查找,所以直接在[\d]{1,3}\.
后面加上{3}
是不可以的
为此我们需要使用元字符( )
来划分子表达式,将其中的所有内容视为一个实体,得到正则表达式如下
/([\d]{1,3}\.){3}[\d]{1,3}
例如我们匹配所有的网址,网址的每一个层级之间是用/
分开的,所以我们可以使用下面的正则表达式匹配
/ht+ps?:\/+([\S]){1,}
但是每中不足的是这里网址最后包含了并不是网址内容的)
,因为我们使用了匹配非空白字符,所以)
也会被匹配上
我们可以修改匹配内容来避免匹配到)
,或者可以使用后面的向前查看来匹配到)
之前
选择匹配
我们可以使用|
元字符来匹配两个指定的字符串
例如
/echo|pyenv
结合前面的划分子表达式,我们可以实现很多强大的功能
前面匹配IP地址的例子,我们只能实现格式上的匹配,即匹配到xxx.xxx.xxx.xxx
而一个有效的IP地址有如下的要求:
- 有一组为一位或两位数组
- 有一组以1开头的三位数字
- 有一组以2开头,第二位数字在0~4之间的三位数字
- 有一组以25开头,第三位在0~5之间的三位数字
所以我们使用如下的正则表达式来匹配有效的IP地址
/(((25[0-5])|(2[0-4]\d)|(1\d{2})|(\d{1,2}))\.){3}
需要注意的是,|
的匹配模式是当多个表达式都满足子表达式时,会选择首先满足的子表达式
例如如果是
/(((\d{1,2})|(25[0-5])|(2[0-4]\d)|(1\d{2}))\.){3}
那么以12.159.46.200为例
最后一组200的20符合第一个子表达式(\d{1,2})
,同时整体又符合(2[0-4]\d)
这个子表达式
但是最终建会优先匹配上第一个符号要求的表达式,即只会选择12.159.46.20
引用匹配
有的时候我们希望能够匹配到成对出现的字符串及之间的内容
例如匹配HTML中的各种标题标签<h1>xxx</h1>
如果直接用下面的表达式
<[Hh][1-6]>*?<\/[Hh][1-6]>
这样除了我们预期的标题标签会被匹配到外,想<h1>xxxx</h2>
这样的错误的标题标签也会被匹配到
对此我们希望成对的匹配内容,例如<h1>xxx</h1?
为此,一个可用的解决方案就是引用前面已经匹配到的内容
即我们使用<[Hh][1-6]>
匹配到前面的<h1>
之后,在后面再次匹配已经匹配到的<h1>
我们使用\数字
来引用已经匹配到的子表达式
例如
/<([Hh][1-6])>*?<\/\1>
其中跟着的数字表示已经匹配到的子表达式的次序,除了\1
以外还可以使用\2
,\3
等
我们匹配markdown中的高亮**xxx**
/(\*+)\1
向前查看
向前查看是指我们按照指定格式一直匹配到我们指定的字符
向前查看的元字符是?=
,需要放在( )
中作为一个子表达式
例如我们匹配所有网址中的协议,即http
或https
/.{0,5}(?=(:\/))
向后查看
和向前查看同理,向后查看表明从指定位置后开始匹配
向前查看的元字符是?<=
,同时也需要放在同一个子表达式中
例如我们匹配网址中的主机名,即:
之后到第一个\
的内容
/(?<=(:\/\/)).+?(?=\/)
所以结合向前向后查看,我们就可以查找任意指定的内容
嵌入式条件
我们可以在正则表达式中嵌入条件,来进行匹配
例如我们需要匹配电话号码时,前四位可以用括号括起来,例如(0915)或0915都是正确的
但是如果是(0915)的话电话号码格式就是(0915)xxxxx,如果是0915的话,电话号码格式就是0915-xxxxx
这个时候我们可以用选择匹配来匹配两个子表达式,但更方便的是,我们可以使用嵌入式条件,当电话号码开头为(0915)时,匹配(0915)xxxxx
当电话号码开头为0915时,匹配0915-xxxxx
类似于任何一门编程语言,我们使用条件判断语句时候有三个重要的组成:
- 判断条件
- 条件成立时的操作
- 条件不成立时的操作
其中条件不成立时的操作是可选的
类似的,正则表达式的条件也分为作为条件的语句,条件语句存在时匹配的语句,条件语句不存在时匹配的语句
正则表达式中,用于表示条件语句的元字符是( )?
我们可以使用(?(数字)成功匹配时匹配的字符|未成功匹配时匹配的字符)
来完成后续匹配
类似于上面说的,条件不成立时的操作可以省略掉,此时后续匹配为(?(数字)成功匹配时的字符)
所以上面匹配座机号,可以使用如下的正则表达式
(\()?\d{4}(?(1)\)|-)\d{5}
上面的正则表达式中(\()?
用于匹配(
,当(
存在时(即第一个匹配存在时),将会匹配对应的)
右括号,否则将匹配-
连字符
条件查看
前面分别讲解了向前查看和向后查看,实际上我们可能需要使用到条件查看
例如,美国邮编有两种形式,分别为五位数字的ZIP编码或者是xxxxx-xxxx的ZIP+4编码
这样的话,我们的环视就需要以-
连字符为条件进行环视,当-
不存在则终止匹配,存在时匹配-xxxx
条件查看的语句为(?(?=查看的字符)查看字符不存在时匹配的字符)
所以匹配美国邮编的正则表达式为
\d{5}(?(?=-)-\d{4})