表达式
正则表达式是一系列表达式的组合
普通字符也是一个表达式
a 是一个正则表达式,匹配字符 a
abc 是一个正则表达式,由三个更小的表达式组合而成 a b c
特殊表达式
\w
字母或数字或下划线
等价于 [a-zA-Z0-9_]
\b
单词开始或结束的<font color="#f68b1f"><b>位置(单词边界)</b></font>,不匹配字符
^
字符串开始的<b><font color="#f1753f">位置</font></b>
$
字符串结束的<b><font color="#f1753f">位置</font></b>
\W, \D, \S, \B
换大写字母表示取反
例如
\D 表示非数字,\B 表示除了单词边界以外的位置
分组<br>(捕获组)
() 小括号表示分组<br>表示把一组表达式组合成一个整体,可以改变表达式顺序<br>一般和 重复 组合使用,分组之后即可对此分组整体进行描述<br>
(abc)*d 表示 abc 重复若干次之后跟着一个字符 d
abc|d 表示 "abc" 或 d
ab(c|d) 表示 "abc" 或 "abd"
命名
分组会被命名
<b><font color="#f1753f" style="">(exp)</font><br></b>自动命名
每个分组都有编号,编号从0开始,编号0的分组表示整个匹配的表达式<br>
自左向右,分组的左括号 "(" 出现的顺序即为该分组的编号
使用 a(b(c(d)e))f 匹配字符串 "abcdefg"<br><ul><li>分组0: "abcdef" 整个匹配到的字符串<br></li><li>分组1: "bcde"<br></li><li>分组2: "cde"<br></li><li>分组3: "d"<br></li></ul>
对于支持正则表达式的编程语言,一般会有用于获取分组的api
例如Java中的groups()
<b><font color="#f1753f">(?<name>exp)</font></b><br>命名捕获组
匹配exp,并捕获文本到名称为name的分组中
(<number>\d+)[a-zA-Z]+ 匹配由数字和字母组成的字符串,并将前半部分的数组捕获,存放在名称为number的分组中
(?:exp)<br>
匹配exp,但是匹配的文本不被存储在任何分组中,仅仅表示exp是一个整体<br>由于捕获组需要存储,对部分正则表达式引擎来说,不存储分组匹配的字符串可以获取性能提升
a(?:bc)+ 表示 a开头并且"bc"至少重复一次的字符串, "abc", "abcbc"<br>分组0表示整个匹配的字符串,分组1不存在
部分正则表达式引擎不支持此特性
(?#comment)
注释分组,会被正则表达式引擎忽略,仅仅提供用于阅读的注释
向后引用
被捕获的分组可以在后续的表达式中被引用
\数字
按照默认的分组编号来引用
(\d).*\1 匹配开始和结束都是数字并且相同的字符串,"1a1", "3asdf43"
abc(\d+)\1 匹配以abc开始,跟着两个由数字组成的相同子串的字符串,"abc11", "abc1212", "abc13241324"
零宽断言*
注意:此特性并非所有正则表达式引擎都支持
<i><font color="#a1a1a1">实际日常使用基本不会需要,后续再补充</font></i>
平衡组*
<i><font color="#a1a1a1">实际日常使用基本不会需要,后续再补充</font></i>
注意
不推荐使用太多的分组,对于一些正则引擎来说,太多的分组在一定情况下会导致匹配效率降低
分组太多时应当修改匹配思路,或者使用多次匹配来处理
替换
匹配到的字符串可以被替换
$分组编号
与自动命名的分组编号一致,可以在替换字符串时引用捕获到的分组
一般使用在支持正则表达式搜索/替换的文本编辑器中
创建正则表达式的一般思路
正则表达式的目的是用来描述一类有共同特点的字符串,最重要的思想是提取字符串中最一般的特点
首先寻找锚点,锚点即需要匹配的字符串中不变的部分<br>
简单一点的为固定的字符组合
例如 "ABSas1", "ABSsdf4" 中 "ABS" 固定不变:ABS
复杂一点的需要使用描述表达式
例如 "123asdfhk", "456asdfkjje" 中 "123", "456" 都是数字:\d{3}
然后逐个描述变化的部分,提取这些变化的部分共有的特性<br>最主要的是范围和数量
例如 "123sdf23123", "7684356sjdfg768", "392asd-q-324a-392"<br>固定的部分是首尾都是数字并且首位相同,变化的部分是中间<br>开头: \d+<br>结尾: \d+ 由于首尾相同,而且都不是简单的固定字符,因此需要使用捕获组和反向引用<br>中间: .*<br>因此整体的表达式为 (\d+).*\1
为了提升效率和减少错误率,可以尽量描述最准确的字符范围
例如上例<br>首尾相同的数字都是三位,因此可以使用 \d{3} 替换 \d+<br>中间,可能出现的字符范围具体是小写字母、数字和短横,并且字符数量在5到11之间因此可以使用 [a-z0-9-]{5,11}<br>最终,更准确的表达式为 (\d{3})[a-z0-9-]{5,11}\1
对于更加复杂的情况(存在多种限制情况),可以使用逻辑或 | 来枚举每种情况
一个很有特点的例子:匹配ip地址
简单的表达式<br>
<font color="#c41230">\d{1,3)</font>\.<font color="#c41230">\d{1,3}</font>\.<font color="#c41230">\d{1,3}</font>\.<font color="#c41230">\d{1,3}</font>
分成四段,每段以点号分隔(需要转义 \.),每段最多三个数字
会错误的匹配999.999.999.999,而这并不是一个合法的ip地址
更准确的表达式
分成四段,每段以点号分隔
每段的数字范围是 0 到 255<br>由于正则中不存在算数运算,需要将数字当做字符来看待<br>因此需要枚举可能的情况
两位数字 10 到 99<br>
[19][0-9]
同 (1[0-9])|(9[0-9])<br>但是明显 [19][0-9] 写法更简洁高效
三位数字 100 到 255<br>
当第一位是 1 时 剩下的两位是 00 到 99
1[0-9]{2}
当第一位是 2 时 剩下的两位是 00 到 55
注意,并不是 2[0-5]{2}<br>因为 00 到 55 包含了 19, 28, 36 等<br>需要再细分
当第二位是 0 到 4 时,第三位是 0 到 9
2[0-4][0-9]
当第二位是 5 时,第三位是 0 到 5
25[0-5]
因此 2[0-4][0-9]|25[0-5]
或者 2([0-4][0-9]|5[0-5])
因此 1[0-9]{2}|2([0-4][0-9]|5[0-5])
因此,描述一个段的表达式是 [0-9]|[19][0-9]|1[0-9]{2}|2([0-4][0-9]|5[0-5])
更清晰的表示
[0-9]<br>一位
|<br>或
[19][0-9]<br>两位
|<br>或
1[0-9]{2}<br>三位, 首位1
|<br>或
2([0-4][0-9]|5[0-5])<br>三位,首位2
因此准确描述一个ip地址(十进制)的表达式是
<font color="#c41230">(</font><font color="#f1753f">[0-9]</font><font color="#c41230">|</font><font color="#924517">[19][0-9]</font><font color="#c41230">|</font><font color="#00a650">1[0-9]{2}|2([0-4][0-9]|5[0-5])</font><font color="#c41230">)</font><font color="#b8b8b8">\.</font><font color="#c41230">(</font><font color="#f1753f">[0-9]</font><font color="#c41230">|</font><font color="#924517">[19][0-9]</font><font color="#c41230">|</font><font color="#00a650">1[0-9]{2}|2([0-4][0-9]|5[0-5])</font><font color="#c41230">)</font><font color="#a1a1a1">\.</font><font color="#c41230">(</font><font color="#f68b1f">[0-9]</font><font color="#c41230">|</font><font color="#924517">[19][0-9]</font><font color="#c41230">|</font><font color="#00a650">1[0-9]{2}|2([0-4][0-9]|5[0-5])</font><font color="#c41230">)</font><font color="#a1a1a1">\.</font><font color="#c41230">(</font><font color="#f68b1f">[0-9]</font><font color="#c41230">|</font><font color="#924517">[19][0-9]</font><font color="#c41230">|</font><font color="#00a650">1[0-9]{2}|2([0-4][0-9]|5[0-5])</font><font color="#c41230">)</font>
可以简化为
(([0-9]|[19][0-9]|1[0-9]{2}|2([0-4][0-9]|5[0-5]))\.){3}([0-9]|[19][0-9]|1[0-9]{2}|2([0-4][0-9]|5[0-5]))
最后,准确是相对的,不存在绝对准确的正则表达式(除非样本允许),满足需求即可,太过复杂的表达式效率会降低
参考
RegexBuddy 可视化工具<br>http://www.regexbuddy.com/
RegexBuddy 工具教程<br>https://www.jianshu.com/p/65f9ccb01b34
菜鸟教程 正则表达式<br>http://www.runoob.com/regexp/regexp-syntax.html
【详细】正则表达式30分钟入门教程<br>https://www.cnblogs.com/sunny3096/p/7201403.html