需要许多行语句才能完成的任务,基本上只需要一个正则表达式,一条语句就可以完成。正因为如此,不少人虽然认为正则表达式不够花哨、漂亮,却不得不承认它是一种“匕首应用”——匕首,没有十八般兵刃那么大方,关键时候却不可或缺,所以值得花时间练练。
正则表达式发源于与计算机密切相关的两个领域:计算理论和形式语言。
UNIX 下的文本编辑器ed最终演化为 grep(grep 得名自 ed 编辑器中的正则表达式搜索命令 g/re/p,其中 re 表示“正则表达式”)
正则表达式可以极大地提高代码的重用度和执行效率。如果完全不使用正则表达式,代码量会增加数倍甚至十倍。
除了 web 开发领域,需要实现大量自动化功能的一些领域,例如运维领域和自动化测试领域,也是正则表达式大显身手的地方。
“面向对象”是“广义相对论”,而“正则表达式”则是“量子力学”
经常需要处理文本的程序员自然会知道正则表达式的价值。
第一部分
第一章 字符组(character set)
字符组就是一组字符,在正则表达式中,表示“在同一个位置可能出现的各种字符”,其写法是在一对方括号[ 和 ] 之间列出所有可能出现的字符,简单的字符组比如 [ab]、[314]、[#.?]
.NET C#
// 匹配:true 不匹配:false
Regex.IsMatch(charStr, "[0123456789]");
Java
// 匹配:true 不匹配:false
charStr.matches("[0123456789]");
JavaScript
// 匹配:true 不匹配:false
/[0123456789]/.test(charStr);
PHP
// 匹配:1 不匹配:0
preg_match("/[0123456789]/", charStr);
Python
# 匹配:RegexObject 不匹配:None
re.search("[0123456789]", charStr)
Ruby
# 匹配:0 不匹配:nil
charStr =~ /[0123456789]/
# 导入正则表达式对应的包
import re
re.search(pattern, string)
^
:定位到字符串的起始位置$
:定位到字符串的结束位置
字符组中的字符排列顺序并不影响字符组的功能,出现重复字符也不会影响
范围表示法:-
表示范围,一般是根据字符对应的码值来确定的,码值小的字符在前,码值大的字符在后。
码值:0~9 是48~57;A~Z是65~90;a~z是97~122;
转义序列:**\x**``_hex_
表示一个字符,**\x**
是固定前缀,表示转义序列的开头,num 是字符对应的码值(Code Point
)
可以表示一些难以输入或者难以显示的字符,
元字符:-``[``]``^``$
在匹配中有特殊的意义。但是有时候并不需要表示这些特殊的意义,就必须通过转义来实现
-
:如果紧邻字符组中的开方括号[,那么它就是普通字符,其它情况下都是元字符
正则表达式中的每个反斜线字符\
,必须转义成 \\
。例如[0\-9]
需要表示成[0\\-9]
原生字符串(Row String):正则表达式是怎样,原生字符串就是怎样,完全不需要考虑正则表达式之外的转义(只有双引号字符是例外,原生字符串内的双引号字符必须转义写成\"
。表达形式:r"string"
r"^[0\-9]$" == "^[0\\-9]$ # true
只希望匹配字符串[012],写成[012]会被识别成字符组,因此需要转义[012],注意只有开方括号需要转义,闭方括号不需要转义。
排除型字符组(Negated Character Class):在当前位置,匹配一个没有列出的字符。表达式:[^...]
- 排除型字符组必须匹配一个字符,即“在当前位置,匹配一个没有列出的字符”,而不是“在当前位置不要匹配列出的字符,即使不出现任何字符也可以”
- ^仅在它紧跟在{之后时是一个元字符,如果不让它紧挨着,就表示“这个字符组中可以出现^字符”
字符组简记法(shorthands):
shorthands | 含义 | 等价于正则表达式 | 备注 | |
---|---|---|---|---|
\d | digit(数字字符) | [0-9] | ||
\w | word(单词字符) | [0-9a-zA-Z_] | 特别注意还包括下划线_ | |
\s | space(空白字符) | [ \t\r\n\v\f] | 空格字符、制表符、回车符、换行符 | |
\d\D | 任意字符 | |||
\w\W | 任意字符 | |||
\s\S | 任意字符 | |||
\n | 换行符 | |||
. | 匹配除换行符以外的任意字符 | |||
\b | 表示单词的开头或结尾,也就是单词的分界处 | 匹配的是位置 | ||
\B | 匹配不是单词开头或结束的位置 | |||
[^x] | 匹配除了x以外的任意字符 | |||
\S+ | 不包含空白符的字符串 | |||
^ | 匹配的是位置 | |||
$ | 匹配的是位置 |
流派:
PCRE: Per Compatible Regular Expression
POSIX: Portable Operating System Interface for unix
第二章 量词
{m,n}
: m 是下限,n是上限,均为闭区间。如果不确定长度的上限,可以省略,只指定下限,写成\d{m,}。量词限定次数一般都有明确下限,如果没有,则默认为0.最好使用{0,n}
量词简记法:
常用量词 | {m,n}等价形式 | 说明 |
---|---|---|
* | {0,} | 可能出现,也可能不出现,出现次数没有上限 |
+ | {1,} | 至少出现1次,出现次数没有上限 |
? | {0,1} | 至多出现1次,也可能不出现 |
分支条件
使用分枝条件时,要注意各个条件的顺序。如果你把它改成\d{5}|\d{5}-\d{4}的话,那么就只会匹配5位的邮编(以及9位邮编的前5位)。原因是匹配分枝条件时,将会从左到右地测试每个条件,如果满足了某个分枝的话,就不会去再管其它的条件了。
分组
用小括号来指定子表达式(也叫做分组),然后你就可以指定这个子表达式的重复次数了,你也可以对子表达式进行其它一些操作
((2[0-4]\d|25[0-5]|[01]?\d\d?)\.){3}(2[0-4]\d|25[0-5]|[01]?\d\d?) # 匹配 IP 地址
每个分组会自动拥有一个组号,规则是:从左向右,以分组的左括号为标志,第一个出现的分组的组号为1,第二个为2,以此类推。
常用分组语法
分类 | 语法 | 说明 |
---|---|---|
捕获 | (exp) | 匹配exp,并捕获文本到自动命名的组里 |
(? |
匹配exp,并捕获文本到名称为name的组里,也可以写成(?’name’exp) | |
(?:exp) | 匹配exp,不捕获匹配的文本,也不给此分组分配组号 | |
零宽断言 | (?=exp) | 匹配exp前面的位置 |
(?<=exp) | 匹配exp后面的位置 | |
(?!exp) | 匹配后面跟的不是exp的位置 | |
(?<!exp) | 匹配前面不是exp的位置 | |
注释 | (?#comment) | 这种类型的分组不对正则表达式的处理产生任何影响,用于提供注释让人阅读 |
反向引用
用于重复搜索前面某个分组匹配的文本。例如,\1代表分组1匹配的文本
贪婪与懒惰
贪婪:匹配尽可能多的字符。
懒惰:匹配尽可能少的字符。在限定符后面添加一个?
正则表达式有另一条规则,比懒惰/贪婪规则的优先级更高:最先开始的匹配拥有最高的优先权
修饰符
- u 修饰符:用于处理 4 个字节的 UTF-16 编码。点(.)字符可以匹配除换行符以外的任意单个字符(对于码点大于0xFFFF的Unicode字符,必须加上u才能识别)。
- y 修饰符:y 修饰符与 g 修饰符类似,也是全局匹配,后一次匹配都是从上一次匹配成功的下一个位置开始。不同之处在于,g 修饰符只要剩余位置中存在匹配就行,而 y 修饰符会确保匹配必须从剩余的第一个位置开始,即“sticky”
- s 修饰符:忽略行终止符,匹配任意单个字符。比点(.)字符匹配范围广,因此称作dotAll模式
- m修饰符:多行修饰符
ES6 RegExp 的属性
- sticky:是否设置了 y 修饰符
- dotAll: 是否设置了 s 修饰符
经典匹配
| 正则表达式 | <(\S?)[^>]>.?</\1>|<.? /> | | —- | —- | | 匹配 | hello|abcd | | 不匹配 | abc|123|ddd |
正则表达式 | <(\S?)[^>]>.?</\1>|<.? /> |
---|---|
匹配 | <html>hello</html>|<a>abcd</a> |
不匹配 | abc|123|<html>ddd |
替换文本 | <$1>$3<$4> |
替换结果 | hello|abcd |
正则表达式 | ^[^<>`~!/@\#}$%:;)(_^{&*=|’+]+$ |
---|---|
匹配 | This is a test |
不匹配 |
正则表达式 | <!—.*?—> |
---|---|
匹配 | <!— <h1>this text has been removed</h1> —> | <!— yada —> |
不匹配 | <h1>this text has not been removed</h1> |
正则表达式 | (\[(\w+)\s(([\w])=(‘|")?([a-zA-Z0-9|:|\/|=|-|.|\?|&])(\5)?)\])([a-zA-Z0-9|:|\/|=|-|.|\?|&|\s]+)(\[\/\2\]) |
---|---|
匹配 | [link url="http://www.domain.com/file.extension?getvar=value&secondvar=value"]Link[/li |
不匹配 | [a]whatever[/b] | [a var1=something var2=somethingelse]whatever[/a] | [a]whatever[a] |
正则表达式 | href=\“\‘?\w+(\.\w+)(\/\w+(\.\w+)?)(\/|\?\w=\w(&\w=\w)*)?[\“\‘] |
---|---|
匹配 | href=”www.yahoo.com” | href=”http://localhost/blah/“ | href=”eek” |
不匹配 | href=”” | href=eek | href=”bad example” |
正则表达式 | "(^"*)" |
---|---|
匹配 | "This is a \"string\"." |
不匹配 | "This is a \"string\". |
正则表达式 | (?i:on(blur|c(hange|lick)|dblclick|focus|keypress|(key|mouse)(down|up)|(un)?load|mouse(move|o(ut|ver))|reset|s(elect|ubmit))) |
---|---|
匹配 | |
不匹配 | click | onandon | mickeymouse |
正则表达式 | (?s)/\.\*/ |
---|---|
匹配 | / ……………….. / | / imagine lots of lines here / |
不匹配 | / malformed opening tag / | / malformed closing tag / |
正则表达式 | <(\S?) [^>]>.?</\1>|<.? /> |
---|---|
匹配 | hello|abcd |
不匹配 | abc|123|ddd |
正则表达式 | \xA9 |
---|---|
匹配 | © |
不匹配 | anything |
正则表达式 | src[^>]*[^/].(?:jpg|bmp|gif)(?:\"|\‘) |
---|---|
匹配 | src="../images/image.jpg" | src="http://domain.com/images/image.jpg" | src=’d:\w |
不匹配 | src="../images/image.tif" | src="cid:value" |
正则表达式 | /\[\d\D]?\*/ |
---|---|
匹配 | / my comment / | / my multiline comment / | / my nested comment / |
不匹配 | / anything here / | anything between 2 seperate comments | \ \ |
正则表达式 | <[a-zA-Z]+(\s+[a-zA-Z]+\s=\s(“([^”])”|’([^’])’))\s/> |
---|---|
匹配 | ![]() |
不匹配 | ![]() ![]() |
一、校验数字的表达式
1 数字:^[0-9]$
2 n位的数字:^\d{n}$
3 至少n位的数字:^\d{n,}$
4 m-n位的数字:^\d{m,n}$
5 零和非零开头的数字:^(0|[1-9][0-9])$
6 非零开头的最多带两位小数的数字:^([1-9][0-9])+(.[0-9]{1,2})?$
7 带1-2位小数的正数或负数:^(-)?\d+(.\d{1,2})?$
8 正数、负数、和小数:^(-|+)?\d+(.\d+)?$
9 有两位小数的正实数:^[0-9]+(.[0-9]{2})?$
10 有1~3位小数的正实数:^[0-9]+(.[0-9]{1,3})?$
11 非零的正整数:^[1-9]\d$ 或 ^([1-9][0-9]){1,3}$ 或 ^+?[1-9][0-9]$
12 非零的负整数:^-[1-9][]0-9”$ 或 ^-[1-9]\d$
13 非负整数:^\d+$ 或 ^[1-9]\d|0$
14 非正整数:^-[1-9]\d|0$ 或 ^((-\d+)|(0+))$
15 非负浮点数:^\d+(.\d+)?$ 或 ^[1-9]\d.\d|0.\d[1-9]\d|0?.0+|0$
16 非正浮点数:^((-\d+(.\d+)?)|(0+(.0+)?))$ 或 ^(-([1-9]\d.\d|0.\d[1-9]\d))|0?.0+|0$
17 正浮点数:^[1-9]\d.\d|0.\d[1-9]\d$ 或 ^(([0-9]+.[0-9][1-9][0-9])|([0-9][1-9][0-9].[0-9]+)|([0-9][1-9][0-9]))$
18 负浮点数:^-([1-9]\d.\d|0.\d[1-9]\d)$ 或 ^(-(([0-9]+.[0-9][1-9][0-9])|([0-9][1-9][0-9].[0-9]+)|([0-9][1-9][0-9])))$
19 浮点数:^(-?\d+)(.\d+)?$ 或 ^-?([1-9]\d.\d|0.\d[1-9]\d|0?.0+|0)$
二、校验字符的表达式
1 汉字:^[\u4e00-\u9fa5]{0,}$
2 英文和数字:^[A-Za-z0-9]+$ 或 ^[A-Za-z0-9]{4,40}$
3 长度为3-20的所有字符:^.{3,20}$
4 由26个英文字母组成的字符串:^[A-Za-z]+$
5 由26个大写英文字母组成的字符串:^[A-Z]+$
6 由26个小写英文字母组成的字符串:^[a-z]+$
7 由数字和26个英文字母组成的字符串:^[A-Za-z0-9]+$
8 由数字、26个英文字母或者下划线组成的字符串:^\w+$ 或 ^\w{3,20}$
9 中文、英文、数字包括下划线:^[\u4E00-\u9FA5A-Za-z0-9]+$
10 中文、英文、数字但不包括下划线等符号:^[\u4E00-\u9FA5A-Za-z0-9]+$ 或 ^[\u4E00-\u9FA5A-Za-z0-9]{2,20}$
11 可以输入含有^%&’,;=?$\”等字符:[^%&’,;=?$\x22]+
12 禁止输入含有~的字符:[^~\x22]+
三、特殊需求表达式
1 Email地址:^\w+([-+.]\w+)@\w+([-.]\w+).\w+([-.]\w+)$
2 域名:[a-zA-Z0-9][-a-zA-Z0-9]{0,62}(/.[a-zA-Z0-9][-a-zA-Z0-9]{0,62})+/.?
3 InternetURL:[a-zA-z]+://[^\s] 或 ^http://([\w-]+\.)+[\w-]+(/[\w-./?%&=]*)?$
4 手机号码:^(13[0-9]|14[0-9]|15[0-9]|16[0-9]|17[0-9]|18[0-9]|19[0-9])\d{8}$ (由于工信部放号段不定时,所以建议使用泛解析 ^([1][3,4,5,6,7,8,9])\d{9}$) 5 电话号码(“XXX-XXXXXXX”、”XXXX-XXXXXXXX”、”XXX-XXXXXXX”、”XXX-XXXXXXXX”、”XXXXXXX”和”XXXXXXXX):^((\d{3,4}-)|\d{3.4}-)?\d{7,8}$
6 国内电话号码(0511-4405222、021-87888822):\d{3}-\d{8}|\d{4}-\d{7}
7 18位身份证号码(数字、字母x结尾):^((\d{18})|([0-9x]{18})|([0-9X]{18}))$
8 帐号是否合法(字母开头,允许5-16字节,允许字母数字下划线):^[a-zA-Z][a-zA-Z0-9]{4,15}$
9 密码(以字母开头,长度在6~18之间,只能包含字母、数字和下划线):^[a-zA-Z]\w{5,17}$
10 强密码(必须包含大小写字母和数字的组合,不能使用特殊字符,长度在8-10之间):^(?=.\d)(?=.[a-z])(?=.[A-Z]).{8,10}$
11 日期格式:^\d{4}-\d{1,2}-\d{1,2}
12 一年的12个月(01~09和1~12):^(0?[1-9]|1[0-2])$
13 一个月的31天(01~09和1~31):^((0?[1-9])|((1|2)[0-9])|30|31)$
14 钱的输入格式:
15 1.有四种钱的表示形式我们可以接受:”10000.00” 和 “10,000.00”, 和没有 “分” 的 “10000” 和 “10,000”:^[1-9][0-9]$
16 2.这表示任意一个不以0开头的数字,但是,这也意味着一个字符”0”不通过,所以我们采用下面的形式:^(0|[1-9][0-9])$
17 3.一个0或者一个不以0开头的数字.我们还可以允许开头有一个负号:^(0|-?[1-9][0-9])$
18 4.这表示一个0或者一个可能为负的开头不为0的数字.让用户以0开头好了.把负号的也去掉,因为钱总不能是负的吧.下面我们要加的是说明可能的小数部分:^[0-9]+(.[0-9]+)?$
19 5.必须说明的是,小数点后面至少应该有1位数,所以”10.”是不通过的,但是 “10” 和 “10.2” 是通过的:^[0-9]+(.[0-9]{2})?$
20 6.这样我们规定小数点后面必须有两位,如果你认为太苛刻了,可以这样:^[0-9]+(.[0-9]{1,2})?$
21 7.这样就允许用户只写一位小数.下面我们该考虑数字中的逗号了,我们可以这样:^[0-9]{1,3}(,[0-9]{3})(.[0-9]{1,2})?$
22 8.1到3个数字,后面跟着任意个 逗号+3个数字,逗号成为可选,而不是必须:^([0-9]+|[0-9]{1,3}(,[0-9]{3}))(.[0-9]{1,2})?$
23 备注:这就是最终结果了,别忘了”+”可以用”“替代如果你觉得空字符串也可以接受的话(奇怪,为什么?)最后,别忘了在用函数时去掉去掉那个反斜杠,一般的错误都在这里
24 xml文件:^([a-zA-Z]+-?)+[a-zA-Z0-9]+\.[x|X][m|M][l|L]$
25 中文字符的正则表达式:[\u4e00-\u9fa5]
26 双字节字符:[^\x00-\xff] (包括汉字在内,可以用来计算字符串的长度(一个双字节字符长度计2,ASCII字符计1))27 空白行的正则表达式:\n\s\r (可以用来删除空白行)
28 HTML标记的正则表达式:<(\S?)[^>]>.?</\1>|<.? /> (网上流传的版本太糟糕,上面这个也仅仅能部分,对于复杂的嵌套标记依旧无能为力)29 首尾空白字符的正则表达式:^\s|\s$或(^\s)|(\s$) (可以用来删除行首行尾的空白字符(包括空格、制表符、换页符等等),非常有用的表达式)
30 腾讯QQ号:[1-9][0-9]{4,} (腾讯QQ号从10000开始)
31 中国邮政编码:[1-9]\d{5}(?!\d) (中国邮政编码为6位数字)
32 IP地址:\d+.\d+.\d+.\d+ (提取IP地址时有用)
33 IP地址:((?:(?:25[0-5]|2[0-4]\d|[01]?\d?\d)\.){3}(?:25[0-5]|2[0-4]\d|[01]?\d?\d)) (由@飞龙三少 提供,感谢共享)
测试工具
在线
http://tool.oschina.net/regex/
https://www.regexpal.com/
https://regex101.com/
安装包
http://deerchao.net/tools/regester/index.htm
资源
- 正则表达式30分钟入门教程 http://deerchao.net/tutorials/regex/regex.htm
- 一个学习、分享、测试正则表达式的平台。对每一个正则表达式,还可以写出逻辑解释,并可以查找别人写的正则表达式来借鉴和使用:https://regexr.com/
- New Fiddle
- Online regex tester and debugger: PHP, PCRE, Python, Golang and JavaScript
- learn-regex:https://github.com/ziishaned/learn-regex/blob/master/translations/README-cn.md
- any-rule 常用正则搜索工具:https://github.com/any86/any-rule