本文介绍基础正则表达式,没有示例(),只有总结和"解惑",如果想学习更强大、更完整的正则,可以参考:Perl正则超详细教程,grep -P
、ack都支持Perl正则,且很多需要使用到正则的服务软件一般都采用PCRE(如httpd、nginx、haproxy、proxysql),它和Perl正则几乎完全一致,作为运维人员,我觉得是有必要学上一学的。
基础正则
元字符:
.
:匹配任意单个字符,但不能匹配换行符\n
*
:匹配前面那个字符0或多次?
:匹配前面那个字符0或一次+
:匹配前面那个字符1次以上{M,N}
:匹配前面那个字符至少M,最多N次{M,}
:匹配前面那个字符至少M次,最多无限制{,N}
:匹配前面那个字符最多N次(最少当然是0次)。注意,perl正则不支持这种方式{M}
:匹配前面那个字符正好M次- 锚定:锚定的意思是匹配位置,而非匹配字符实体
^
:匹配行首位置,注意匹配的是位置,不是字符$
:匹配行尾位置,注意匹配的是位置,不是字符
特殊且常用的的组合正则表达式:
^$
:它表示匹配空行.*
:匹配任意长度的任意字符,但不能匹配换行符。真正的匹配任意长度的任意字符,见下面
需要解释清楚的是这些量词(也就是上面匹配的次数元字符)的特殊性:当使用了匹配多次的量词时(如匹配3-5次的{3,5}
),且量词前面的字符有多种可能性(如中括号序列[abc]
),那么量词的次数可以作用于任一字符。有些不好理解,但看示例就知道了:
[abc]{3,5} # 表示abc任意字符都可以出现,比如全是a,或者ab同时出现,但总的出现次数为3-5次
.* # 表示任一字符(除换行符),可以任意出现任意次数,它不表示a之后就必须全是a
.+ # 表示任一字符(除换行符),可以任意出现至少一次,它不表示a之后就必须全是a
另外,.
无法匹配换行符。可能你不太理解为什么需要匹配换行符,它主要用在:
- 多行模式。例如sed的多行模式下,要跨行匹配需要手动指定"\n",如
/^a.*\nb.*/
- 明确指定了行分隔符为非"\n"的情况。例如awk可以使用RS变量指定输入行分隔符
中括号
中括号表示的是匹配任意一个,一般它和字符集的排序规则有关,不同工具采取的排序规则可能也不一样。
[abcd...]
:匹配中括号内的任意一个字符[^abcd...]
:拒绝匹配中括号内的任意字符[a-z]
:匹配字母a到z[A-Z]
:匹配字母A到Z[0-9]
:匹配0-9,也就是匹配数字
关于字母的排序:
- perl中,A-Z排在a的前面,所以[A-z]表示所有大小写字母
- grep中,A-Z排在z的后面,所以[a-Z]表示所有大小写字母
- 还有些工具中,大小写的排序规则是aAbBcC...zZ,所以[a-C]表示aAbBcC共6个字母
字符类
是专门命名的中括号序列;除了字符类,还有等价类、排序类,但基本用不上,只用字符类。
[:alpha:]
:匹配字母,等价于[a-zA-Z]
[:digit:]
:匹配数字,等价于[0-9]
[:xdigit:]
:匹配十六进制数,等价于[0-9a-fA-F]
[:upper:]
:匹配大写字母,等价于[A-Z]
[:lower:]
:匹配小写字母,等价于[a-z]
[:alnum:]
:匹配数字或字母,等价于[0-9a-zA-Z]
[:blank:]
:匹配空白,包括空格和制表符[:space:]
:匹配空格,包括空格、制表符、换行符、回车符等各种类型的空白[:punct:]
:匹配标点符号。包括:! ' " ` # $ % & ( ) * + , . - _ / : ; < = > ? @ [ \ ] ^ { | } ~
[:graph:]
:绘图类。包括:大小写字母、数字和标点符号。等价于[:alnum:]
+[:punct:]
[:print:]
:打印字符类。包括:大小写字母、数字、标点符号和空格。等价于[:alnum:]
+[:punct:]
+space[:cntrl:]
:控制字符类。在ASCII中,这些字符的八进制代码从000到037,还包括177(DEL)
需要注意的是,通常字符类在真正使用过程中,会再加上一个中括号,例如[[:alpha:]]
。之所以如此,是因为这些字符类只是一种命名好的字符集合。例如[:lower:]
对应的字符集合是a-z,而不是[a-z]
,所以要想让其表示这些命名字符类中的任一字符,需要再加上一层括号[[:lower:]]
,它才等价于[a-z]
。可能会更有助于理解使用字符类的时候为什么要加两个中括号的例子是[^[:lower:]]
,它表示不包含任何小写字母。
反斜线序列
不同的工具,同一工具不同的版本,支持的反斜线序列能力不同。以下列出了部分常见序列。
以下所说的单词,一般来说只包含数字、字母和下划线,即[_0-9a-zA-Z]
。
以下几种反斜线序列,基本上所有工具都支持:
\b
:匹配单词边界处的空字符\B
:匹配非单词边界处的空字符\<
:匹配单词开头处的空字符\>
:匹配单词结尾处的空字符\w
:匹配单词构成部分,等价于[_[:alnum:]]
\W
:匹配非单词构成部分,等价于[^_[:alnum:]]
以下几种,有些工具不支持,但perl都支持:
\s
:匹配空白字符,等价于[[:space:]]
\S
:匹配非空白字符,等价于[^[:space:]]
\d
:匹配数字,等价于[0-9]
\D
:匹配非数字,等价于[^0-9]
由于元字符.
默认无法匹配换行符,所以需要匹配换行符的时候,可以使用特殊组合[\d\D]
来替换.
,换句话说,如果想匹配任意长度的任意字符,可以换成[\d\D]*
,当然,前提是必须支持\d
和\D
两个反斜线序列。
分组捕获和反向引用
基础正则中,使用括号可以对匹配内容进行分组并暂时保存,分组后会有分组编号,可以使用反斜线加编号\N
的方式反向引用这些分组。
分组编号的方式是从左向右计算括号数,无论如何嵌套,第一个左括号对应的分组一定是编号1,用\1
来引用,第二个左括号对应的分组一定是编号2,用\2
来引用,依此类推。
例如grep的分组捕获:匹配两个连续相同的字母。
echo "abcddefg" | grep -E "(.)\1"
可以认为分组就是变量赋值的过程。例如,上面示例的匹配过程如下:
1.匹配第一个字母a,放进分组,即赋值给变量(假设变量名为$1),即$1="a"
,再继续执行正则表达式匹配过程的反向引用,它引用的是$1,于是表示第一个字母a后面还要是字母a,于是匹配失败。
2.匹配第二个字母b,放进分组,即$1="b"
,再匹配后一个字母,于是匹配失败。
3.字母c同样如此。
4.匹配字母d,放进分组,即$1="d"
,再匹配后一个字母,发现匹配成功,于是$1被保存下来。
5.已经匹配成功,于是结束。
对于只使用基础正则的工具来说,一般都只能引用\1
到\9
共9个反向引用,最多自己额外提供一个所有表示匹配内容的反向引用(例如sed提供的&
)。对于超出10个的分组,使用基础正则的工具一般来说是无能为力的。
再者,基础正则仅仅只是将分组匹配到的内容捕获,在正则操作结束后就丢失。但对于一门完整编程语言来说,这远远不够,几乎所有编程语言(如perl/Java/Python等)都会将正则的分组匹配内容保存为变量,使得可以在正则结束之后再次引用甚至修改它们。例如上面例子中分组捕获的字母d,如果换成perl,即使在这个匹配过程结束后,还是可以去引用这段分组。
二选一
pattern1 | pattern2
:匹配竖线左边,或者匹配竖线右边都算匹配成功
关于二选一的结构,几点需要说明:
- 因为竖线元字符的优先级很低,所以
ab|cd
匹配的是"ab"或"cd",而不是abd或acd。 - 成功匹配了左边,就不会再去对右边进行匹配。
- 反向引用失败问题:竖线将两边的分组隔开,右边的永远无法反向引用左边的分组
在二选一结构种,两个反向引用问题的典型例子:
例如a(.)|b\1
将无法匹配"ba",因为评估了左边就不会评估右边。
例如([ac])e\1|b([xyz])\2t
的左边能匹配aea或cec,但不能匹配cea或aec,右边能匹配bxxt或byyt或bzzt。但如果将\2
换成\1
,即([ac])e\1|b([xyz])\1t
,将无法匹配b[xyz]at或b[xyz]ct,因为第一个分组括号在左边,无法参与右边的正则评估。
速记理解技巧
. |
[ ] |
^ |
$ |
四个字符是所有语言都支持的正则表达式,所以这四个是基础的正则表达式。正则难理解因为里面有一个等价的概念,这个概念大大增加了理解难度,让很多初学者看起来会懵,如果把等价都恢复成原始写法,自己书写正则就超级简单了,就像说话一样去写你的正则了:
等价:
等价是等同于的意思,表示同样的功能,用不同符号来书写。
?,*,+,\d,\w 都是等价字符
?等价于匹配长度{0,1}
*等价于匹配长度{0,}
+等价于匹配长度{1,}
\d等价于[0-9]
\D等价于[^0-9]
\w等价于[A-Za-z_0-9]
\W等价于[^A-Za-z_0-9]。
常用运算符与表达式:
^ 开始
() 域段
[] 包含,默认是一个字符长度
[^] 不包含,默认是一个字符长度
{n,m} 匹配长度
. 任何单个字符(\. 字符点)
| 或
\ 转义
$ 结尾
[A-Z] 26个大写字母
[a-z] 26个小写字母
[0-9] 0至9数字
[A-Za-z0-9] 26个大写字母、26个小写字母和0至9数字
, 分割
.
分割语法:
[A,H,T,W] 包含A或H或T或W字母
[a,h,t,w] 包含a或h或t或w字母
[0,3,6,8] 包含0或3或6或8数字
语法与释义:
基础语法 "^([]{})([]{})([]{})$"
正则字符串 = "开始([包含内容]{长度})([包含内容]{长度})([包含内容]{长度})结束"
?,*,+,\d,\w 这些都是简写的,完全可以用[]和{}代替,在(?:)(?=)(?!)(?<=)(?<!)(?i)(*?)(+?)这种特殊组合情况下除外。
初学者可以忽略?,*,+,\d,\w一些简写标示符,学会了基础使用再按表自己去等价替换
实例:
字符串;tel:086-0666-88810009999
原始正则:"^tel:[0-9]{1,3}-[0][0-9]{2,3}-[0-9]{8,11}$"
速记理解:开始 "tel:普通文本"[0-9数字]{1至3位}"-普通文本"[0数字][0-9数字]{2至3位}"-普通文本"[0-9数字]{8至11位} 结束"
等价简写后正则写法:"^tel:\d{1,3}-[0]\d{2,3}-\d{8,11}$" ,简写语法不是所有语言都支持。
折叠应用实例
【1】正则表达式应用--替换指定内容到行尾
原始文本如下面两行
abc aaaaa
123 abc 444
希望每次遇到"abc",则替换"abc"以及其后到行尾的内容为"abc efg"
即上面的文本最终替换为:
abc efg
123 abc efg
解决:
① 在替换对话框,查找内容里输入"abc.*",替换内容输入为"abc efg"
② 同时勾选"正则表达式"复选框,然后点击"全部替换"按钮
其中,符号的含义如下:
"." =匹配任意字符
"*" =匹配0次或更多
注意:其实就是正则表达式替换,这里只是把一些曾经提出的问题加以整理,单纯从正则表达式本身来说,就可以引申出成千上万种特例。
【2】正则表达式应用--数字替换
希望把
asdadas123asdasdas456asdasdasd789asdasd
替换为:
asdadas[123]asdasdas[456]asdasdasd[789]asdasd
在替换对话框里面,勾选"正则表达式"复选框;
在查找内容里面输入"([0-9])([0-9])([0-9])",不含引号
"替换为:"里面输入"[\1\2\3]",不含引号
####备注####:查找([0-9]+) 替换:[\1] 更简单通用些
范围为你所操作的范围,然后选择替换即可。
实际上这也是正则表达式的使用特例,"[0-9]"表示匹配0~9之间的任何特例,同样"[a-z]"就表示匹配a~z之间的任何特例
上面重复使用了"[0-9]",表示连续出现的三个数字
括号用来选择原型,进行分组,替换时要用
"\1"代表第一个"[0-9]"对应的原型,"\2"代表第二个"[0-9]"对应的原型,依此类推
"["|"]"为单纯的字符,表示添加"["或"]",如果输入"其它\1\2\3其它",则替换结果为:
asdadas其它123其它asdasdas其它456其它asdasdasd其它789其它asdasd
功能增强:
如果将查找内容"[0-9][0-9][0-9]"改为"[0-9]*[0-9]",对应1 或 123 或 12345 或 ...
大家根据需要定制
相关内容还有很多,可以自己参考正则表达式的语法仔
【3】正则表达式应用--删除每一行行尾的指定字符
因为这几个字符在行中也是出现的,所以肯定不能用简单的替换实现
比如
12345 1265345
2345
需要删除每行末尾的"345"
这个也算正则表达式的用法,其实仔细看正则表达式应该比较简单,不过既然有这个问题提出,说明对正则表达式还得有个认识过程,解决方法如下
解决:
在替换对话框中,启用"正则表达式"复选框
在查找内容里面输入"345$"
这里"$"表示从行尾匹配
如果从行首匹配,可以用"^"来实现,不过 EditPlus 有另一个功能可以很简单的删除行首的字符串
a. 选择要操作的行
b. 编辑-格式-删除行注释
c. 在弹出对话框里面输入要清除的行首字符,确定
【4】正则表达式应用--替换带有半角括号的多行
几百个网页中都有下面一段代码:
<script LANGUAGE="JavaScript1.1">
<!--
htmlAdWH('93163607', '728', '90');
//-->
</SCRIPT>
我想把它们都去掉,可是找了很多search & replace的软件,都是只能对"一行"进行操作。
EditPlus 打开几百个网页文件还是比较顺畅的,所以完全可以胜任这个工作。
具体解决方法,在 Editplus 中使用正则表达式,由于"("、")"被用做预设表达式(或者可以称作子表达式)的标志,所以查找
"<script LANGUAGE="JavaScript1.1">\n<!--\nhtmlAdWH('93163607', '728', '90'.);\n//-->\n</SCRIPT>\n"
时会提示查找不到,所以也就无法进行替换了,这时可以把"("、")"使用任意字符标记替代,即半角句号:"."。替换内容为
<script LANGUAGE="JavaScript1.1">\n<!--\nhtmlAdWH.'93163607', '728', '90'.;\n//-->\n</SCRIPT>\n
在替换对话框启用"正则表达式"选项,这时就可以完成替换了
补充:
对( ) 这样的特殊符号,应该用\( \)来表示,这也是很标准的regexp语法,可以写为
<script LANGUAGE="JavaScript1.1">\n<!--\nhtmlAdWH\('93163607', '728', '90'\);\n//-->\n</SCRIPT>\n
【5】正则表达式应用--删除空行
启动EditPlus,打开待处理的文本类型文件。
①、选择"查找"菜单的"替换"命令,弹出文本替换对话框。选中"正则表达式"复选框,表明我们要在查找、替换中使用正则表达式。然后,选中"替换范围"中的"当前文件",表明对当前文件操作。
②、单击"查找内容"组合框右侧的按钮,出现下拉菜单。
③、下面的操作添加正则表达式,该表达式代表待查找的空行。(技巧提示:空行仅包括空格符、制表符、回车符,且必须以这三个符号之一作为一行的开头,并且以回车符结尾,查找空行的关键是构造代表空行的正则表达式)。
直接在"查找"中输入正则表达式"^[ \t]*\n",注意\t前有空格符。
(1)选择"从行首开始匹配","查找内容"组合框中出现字符"^",表示待查找字符串必须出文本中一行的行首。
(2)选择"字符在范围中",那么在"^"后会增加一对括号"[]",当前插入点在括号中。括号在正则表达式中表示,文本中的字符匹配括号中任意一个字符即符合查找条件。
(3)按一下空格键,添加空格符。空格符是空行的一个组成成分。
(4)选择"制表符",添加代表制表符的"\t"。
(5)移动光标,将当前插入点移到"]"之后,然后选择"匹配 0 次或更多",该操作会添加星号字符"*"。星号表示,其前面的括号"[]"内的空格符或制表符,在一行中出现0个或多个。
(6)选择"换行符",插入"\n",表示回车符。
④、"替换为"组合框保持空,表示删除查找到的内容。单击"替换"按钮逐个行删除空行,或单击"全部替换"按钮删除全部空行(注意:EditPlus有时存在"全部替换"不能一次性完全删除空行的问题,可能是程序BUG,需要多按几次按钮)。
【6】 正则表达式应用--实例应用
1.验证用户名和密码:("^[a-zA-Z]\w{5,15}$")正确格式:"[A-Z][a-z]_[0-9]"组成,并且第一个字必须为字母6~16位;
2.验证电话号码:("^(\d{3,4}-)\d{7,8}$")正确格式:xxx/xxxx-xxxxxxx/xxxxxxxx;
3.验证手机号码:"^1[3|4|5|7|8][0-9]{9}$";
4.验证身份证号(15位):"\d{14}[[0-9],0-9xX]",(18位):"\d{17}(\d|X|x)";
5.验证Email地址:("^\w+([-+.]\w+)*@\w+([-.]\w+)*\.\w+([-.]\w+)*$");
6.只能输入由数字和26个英文字母组成的字符串:("^[A-Za-z0-9]+$");
7.整数或者小数:^[0-9]+([.][0-9]+){0,1}$
8.只能输入数字:"^[0-9]*$"。
9.只能输入n位的数字:"^\d{n}$"。
10.只能输入至少n位的数字:"^\d{n,}$"。
11.只能输入m~n位的数字:"^\d{m,n}$"。
12.只能输入零和非零开头的数字:"^(0|[1-9][0-9]*)$"。
13.只能输入有两位小数的正实数:"^[0-9]+(\.[0-9]{2})?$"。
14.只能输入有1~3位小数的正实数:"^[0-9]+(\.[0-9]{1,3})?$"。
15.只能输入非零的正整数:"^\+?[1-9][0-9]*$"。
16.只能输入非零的负整数:"^\-[1-9][0-9]*$"。
17.只能输入长度为3的字符:"^.{3}$"。
18.只能输入由26个英文字母组成的字符串:"^[A-Za-z]+$"。
19.只能输入由26个大写英文字母组成的字符串:"^[A-Z]+$"。
20.只能输入由26个小写英文字母组成的字符串:"^[a-z]+$"。
21.验证是否含有^%&',;=?$\"等字符:"[%&',;=?$\\^]+"。
22.只能输入汉字:"^[\u4e00-\u9fa5]{0,}$"。
23.验证URL:"^http://([\w-]+\.)+[\w-]+(/[\w-./?%&=]*)?$"。
24.验证一年的12个月:"^(0?[1-9]|1[0-2])$"正确格式为:"01"~"09"和"10"~"12"。
25.验证一个月的31天:"^((0?[1-9])|((1|2)[0-9])|30|31)$"正确格式为;"01"~"09"、"10"~"29"和"30"~"31"。
26.获取日期正则表达式:\\d{4}[年|\-|\.]\d{\1-\12}[月|\-|\.]\d{\1-\31}日?
评注:可用来匹配大多数年月日信息。
27.匹配双字节字符(包括汉字在内):[^\x00-\xff]
评注:可以用来计算字符串的长度(一个双字节字符长度计2,ASCII字符计1)
28.匹配空白行的正则表达式:\n\s*\r
评注:可以用来删除空白行
29.匹配HTML标记的正则表达式:<(\S*?)[^>]*>.*?</>|<.*? />
评注:网上流传的版本太糟糕,上面这个也仅仅能匹配部分,对于复杂的嵌套标记依旧无能为力
30.匹配首尾空白字符的正则表达式:^\s*|\s*$
评注:可以用来删除行首行尾的空白字符(包括空格、制表符、换页符等等),非常有用的表达式
31.匹配网址URL的正则表达式:[a-zA-z]+://[^\s]*
评注:网上流传的版本功能很有限,上面这个基本可以满足需求
32.匹配帐号是否合法(字母开头,允许5-16字节,允许字母数字下划线):^[a-zA-Z][a-zA-Z0-9_]{4,15}$
评注:表单验证时很实用
33.匹配腾讯QQ号:[1-9][0-9]{4,}
评注:腾讯QQ号从10 000 开始
34.匹配中国邮政编码:[1-9]\\d{5}(?!\d)
评注:中国邮政编码为6位数字
35.匹配ip地址:([1-9]{1,3}\.){3}[1-9]。
评注:提取ip地址时有用
36.匹配MAC地址:([A-Fa-f0-9]{2}\:){5}[A-Fa-f0-9]
Function IsRegu(Regu,s)
'正则表达式校验
If Regu="" Then
Exit Function
End if
Dim Re,Sre
Set Re = New RegExp
Re.Pattern = Regu
Sre = Re.Test(s)
If Sre = True Then
IsRegu = True
Else
IsRegu = False
End If
End Function
tmp=" "
if (IsRegu("\w+([-+.]\w+)*@\w+([-.]\w+)*\.\w+([-.]\w+)*",tmp )) =false then
msgbox "E-mail地址不合法 !"
FieldCheck#N=false
不同的语言(如PHP和JAVA)、相同语言的不同类库(如来自Sun的Java Regular Expression类库跟Apache Jakarta的正则表达式类库)间,用法会有所差别,在使用的时候,要注意这些差别。