正则表达式RegEx
正则表达式(Regular Expression, RegEx)本身是一个字符串,它通过元字符(类似于通配符)描述/匹配具有特定特征模式的字符串。其起源最早则可追溯到人们对于神经系统工作原理的早期研究:1956年,数学科学家Stephen Cole Kleene最先引入正则表达式,用于描述MP神经元模型;随后被Unix之父Ken Thompson用于Unix上的文本编辑器中;最终正则表达式在计算机领域发展普及,广泛用于字符串的核对/查找/提取/替换,现在很多程序语言及文本工具都内置正则表达式支持。
参考:
https://baike.baidu.com/item/正则表达式/
http://www.cnblogs.com/zhuzhenwei918/p/6196661.html
http://aperise.iteye.com/blog/2302031
http://deerchao.net/tutorials/regex/regex.htm
精通正则表达式 Mastering Regular Expression
Perl语言入门 Learning Perl
刨根究底正则表达式
入门简介
通配符
正则表达式与通配符在思想上很类似,都是要筛选出满足特定模式的字符串。但两者并不相同,也不存在包含关系,前者可以看做后者在思想上的进一步拓展,更加强大灵活,具体来说:
- 正则表达式用于选择文本中特定模式的字符串,通配符则一般用于选择特定模式的文件名或文件路径;
- 正则表达式是包含匹配,匹配字符串中符合特定模式的子序列;通配符是对整个字符串的完全匹配;
- 正则表达式由具体实现的正则表达式引擎解析,通配符则由shell解释器解析;
- Linux常用命令中支持正则表达式有grep、awk、sed,支持通配符的有ls、find、cp。
字符 | 说明 |
---|---|
* | 匹配任意字符 |
? | 匹配任一字符 |
[…] | 匹配中括号内字符集中的任一字符 |
[!..] | 匹配中括号内字符集之外的任一字符 |
流派分类
正则表达式只有一个大致的标准,根据具体的实现各有不同,可以分为不同的流派,基本的有:
- 基本的正则表达式(Basic Regular Expression 又叫 Basic RegEx 简称
BRE
) - 扩展的正则表达式(Extended Regular Expression 又叫 Extended RegEx 简称
ERE
,ereg
) - Perl的正则表达式(Perl Regular Expression 又叫 Perl RegEx 简称
PRE
,preg
)
BRE是最基本的正则表达式;ERE与BRE类似,且表达更加简单;而作为Perl语言的一大特色,Perl的正则表达式十分强大,且影响深远,演化出了PCRE(Perl Compatible Regular Expressions, Perl语言兼容正则表达式),一个由C语言编写、为很多现代语言和工具所使用的正则库,俨然已成为正则表达式的事实标准。大多数现代语言的正则表达式都与Perl语言的类似,如PHP便使用PCRE实现正则表达式。
引擎分类
从正则表达式的解析引擎分,又可分为:DFA(Deterministic Finite Automaton, 确定有穷状态自动机)、NFA(Nondeterministic Finite Automaton, 不确定有穷状态自动机)
- DFA不提供回溯功能(Backtrack),速度较快;NFA提供了回溯功能,速度较慢
- NFA是正则导向的(Regex-Directed),而DFA是文本导向的(Text-Directed)
- 确定有限自动机(DFA),“确定”是指在任意时刻DFA必定处于某个确定的状态;而NFA则可能处于一组状态中的任何一个,因此是“不确定”的。为此NFA引擎不仅要记录状态本身,还必须记录众多到达特定状态的可能路径,这也是NFA能够提供回溯功能的原因。
NFA引擎拥有如惰性量词(lazy quantifiers)和反向引用(backreferences)等实用特性,因此目前更流行;在Unix引入POSIX标准后,还出现了POSIX NFA变种。
- 采用NFA的主要有:多数版本grep、部分版本egrep, awk、sed、less、more、vi、Emacs、Perl、PCRE、Python、Ruby、PHP、Java、.NET;
- 采用DFA的主要有:多数版本egrep, awk、lex、flex;
- 还有一些采用混合引擎(如Tcl),根据任务的不同选择合适的引擎(甚至对同一表达式中的不同部分采用不同的引擎,以求得功能与速度之间的平衡)。
DFA引擎不要求回溯,在线性时状态下执行:永远不会测试相同的字符两次,并可确保匹配最长的可能字符串。但由于DFA引擎只包含有限的状态,所以不能匹配具有反向引用的模式;并且因为DFA引擎不构造显示扩展,也不可以捕获子表达式。
NFA引擎运行所谓的贪婪匹配回溯算法,以指定顺序测试正则表达式的所有可能的扩展并接受第一个匹配项。因为NFA引擎构造正则表达式的特定扩展以获得成功的匹配,所以它可以捕获子表达式匹配和进行反向引用。因为回溯,NFA引擎会访问完全相同的状态多次(通过不同的路径),最坏情况下执行速度会很慢。又因其接受找到的第一个匹配,还可能会导致其他(可能更长)匹配未被发现。POSIX NFA引擎与传统NFA引擎类似,但在找到最长的可能匹配前会继续回溯,因此速度比传统NFA引擎更慢。
https://blog.csdn.net/yangzhongxuan/article/details/6968556
https://www.cnblogs.com/zhuimengdeyuanyuan/archive/2013/02/06/2893240.html
传统型NFA正则引擎由于遵循“最左先到先得原则”,一旦其中某个分支获得了匹配,将不会继续尝试匹配剩下的分支;而DFA和POSIX NFA正则引擎由于遵循“最左最长原则”,必须选择各个分支中所获得的最长匹配,因此会逐个分支尝试匹配)。
实用工具
在线测试
:Regex 101; Regex Pal
可视化
:Debuggex; regexper; Regulex #后两个只支持JavaScript
正则伴侣
:RegexBuddy #理解学习测试优化正则表达式,收费
元字符概述
分类
正则表达式符号可分为两类:元字符及限定符。
元字符是指正则表达式中有特殊作用,不代表它字面意义的字符,
具体的可分为匹配字符、定位字符(锚点)、限定符及模式修饰符
匹配字符用于匹配特定字符,定位字符则
修饰符用在正则表达式结尾,设置匹配模式(不区分大小写,全局匹配等)
元字符用于匹配字符或位置(锚点);限定符则专门用于限定其前面一个字符(串)的出现次数,本身不具独立意义。
字符类别 | Perl RegEx |
---|---|
匹配字符 | .; […], [^…], [ - ]; \ ; \w, \W, \d, \D; \s, \S, \cx, \xmn; \f\n\r\t\v |
运算符 | ( ), (?:…), (?<name>…); |; \m, $m; |
限定符(量词 quantifier) | ? * + {n} {n,} {n,m} |
定位字符(锚位 anchor) | ^, $, \b \B,\A, \z, \Z, \G |
模式修饰符 flags | /g, /s , /m, /i, /x, /U |
零宽断言 | (?=), (?<=), (?!), (?<!) |
优先级
优先级由高到低 | 示例 |
---|---|
分组 | (…), (?:…), (?<name>…) |
量词 | a*, a+, a?, a{n,m} |
序列和锚位 | abc, ^, $, \b \B,\A, \z, \Z, \G |
或 | a|b|c |
基本单元 | 字符a; 字符集[abc], \d; 反选[^abc]; 引用\m, \g{m} |
规则详解
以Perl语言正则表达式为例
定界符
模式匹配操作符m/.../
用于限定正则表达式的范围,通常会简写做只保留定界符(delimiter)/.../
定界符最常见的选择便是/.../
,但实际上除字母数字及反斜线\外的任何字符都可以作为定界符号,只要前后成对出现即可,如##、||、{}、!!、%%等等。
使用/
做定界符时,若匹配内容中包含/
需进行转义,因此会见到如/http:\/\//
的网址匹配表达式,更方便的做法是选择/
之外的字符做定界符。
匹配字符
注意:正则表达式与通配符不同,所有的匹配字符
默认都只匹配单一字符,不存在类似通配符中*匹配任意字符的情况;对于满足匹配规则的字符:出现的次数由限定符
限制,出现的位置由位置符
限制
字符 | 说明 |
---|---|
. | 匹配除换行符\n 外的任一字符(有且只有一个,相当于通配符的?) |
[…] | 字符集 ,匹配字符集中的任一字符,如[Abc] |
[^…] | 反选 ,匹配字符集外的任一字符,如[^Abc] |
[ - ] | 范围 ,如[0-9]表示任一数字, [A-Z]表示任一大写字母 |
[ && ] | 交集 ,多个范围连用默认取并集,如[abc], [A-Za-z];交集需用&& [a-z&&[^d-m]]<=>[a-dm-z] |
\ | 转义 ,消除字符的特殊含义,如\\ , \* |
\w | 匹配任一单词字符(字母数字下划线)<=>[A-Za-z0-9_] |
\W | 匹配任一特殊字符(非单词字符, 与\w相反)<=>[^A-Za-z0-9_], [^\w] |
\d | 匹配任一数字<=>[0-9]) |
\D | 匹配任一非数字字符<=>[^0-9] |
\s | 匹配任一空白字符(空格制表等)<=>[\f\t\n\r\v] |
\S | 匹配任一非空白字符<=>[^\f\t\n\r\v] |
\h | 匹配任一水平空白字符<=>[\f\t\n\r] |
\f\n\r\t\v | 空白字符: 空格?, \f(换页), \n(换行), \r(回车), \t(制表), \v(垂直制表) |
\cX | 匹配控制字符cX,如\cx 代表Control-x ,\cM 代表Control-M 或Enter |
\xmn | 匹配ASCII编码为mn(2个十六进制)的字符,如[^\x00-\xff]匹配非ASCII字符(双字节字符) |
\uhhhh | 匹配Unicode编码为hhhh(4个十六进制)的字符,如[\u4e00-\u9fa5]匹配汉字 |
\p{name} | 匹配Unicode编码中命名为name的字符类,如p{Space}<=>[\f\t\n\r\v] |
搜索backreferences、Table
运算符
字符 | 说明 |
---|---|
(…) | 分组 ,将( )内字符串或子表达式视为整体匹配 |
(?<name>…) | 命名分组 ,给分组命名为name,方便通过名字引用;Python语法为(?P<name>…) |
(?:…) | 无记忆分组 分组,但不参与捕获 |
| | 或 ,匹配多个子表达之一,如x|y |
\m | 引用 ,m是正整数,表示匹配期间对第m个分组匹配结果的引用 \g{m} , g for group |
$m | 捕获变量 ,表示模式匹配结束后对第m个分组匹配结果的引用 |
1 | 嵌入条件分组(?(condition)true_sub-regex|false_sub-regex) |
分组( )
也被称为捕获组,因为( )
出现会触发正则表达式引擎的“捕获”:将匹配到的字符串(而非模式本身)保存在特殊缓存中,除非使用(?:)
禁止捕获。多个括号会对应有多个捕获组。
我们可以通过反向引用\m
获取捕获的内容,也可以在匹配操作结束后通过捕获变量$m
取得捕获内容:前者引用的是捕获期间的结果,后者则是模式匹配结束后的结果。
分组序号m
为左括号(包含嵌套括号)的序号。捕获变量通常能存活到下次匹配成功。
此外还有三个自动的捕获变量,就算不加圆括号也能使用,
限定符(量词)
字符 | 说明 |
---|---|
? | 表示前一字符(串)出现0或1次({0,1}) |
* | 表示前一字符(串)出现0或多次({0, }),如.* 便可匹配任意多任意字符,相当于通配符* |
+ | 表示前一字符(串)出现1或多次({1, }) |
{n} | 表示子表达式出现n次,n为非负整数 |
{n,} | 表示子表达式至少出现n次 |
{n,m} | 表示子表达式出现n~m次(最少n最多m),m n为非负整数且n<=m,注意n,m之间不能有空格 |
??, *?, +?, {n}?, {n,}?, {n,m}? |
惰性量词 /惰性限定符 :限定符加? |
?+, *+, ++, {n}+, {n,}+, {n,m}+ |
支配量词 /占有模式 :限定符加+ |
https://www.cnblogs.com/tannerBG/p/5756892.html
- 惰性匹配下,会尽可能少的匹配字符(单个字符),若匹配失败则逐步扩大匹配范围,直至匹配成功;与之相对,不加?默认是贪婪匹配,会尝试尽可能多地匹配字符(整个字符串),若匹配失败则逐步缩小匹配范围,直至匹配成功;而加
+
的占有模式下,同样会尝试尽可能多地匹配字符(整个字符串),且匹配失败就不再尝试。因此惰性模式得到最小匹配,贪婪模式得到最大匹配,占有模式则是对整个字符串的完全匹配。
比如字符串a<tr>aava </tr>abb
:<.+>
会匹配到<tr>aava </tr>
,<.+?>
会匹配到<tr>
和</tr>
,<.++>
则匹配失败
又比如字符串dxxxddxxd
,(d)(\w+)(d)
会匹配到dxxxddxxd
,(d)(\w+?)(d)
会匹配到dxxxd
和dxxd
,(d)(\w++)(d)
同样匹配到dxxxddxxd
,而(d)(\w+?)
只会匹配到dx
,dd
位置符(锚位)
字符 | 说明 |
---|---|
^ | 匹配行首 |
$ | 匹配行尾 |
^$ | 匹配空行 |
^string$ | 匹配内容为string的行 |
\b | 匹配词首词尾(单词边界) |
\B | 匹配非边界(词首尾)的字串 |
\A | 匹配必须出现在字符串绝对开头 |
\z | 匹配必须出现在字符串绝对结尾,其后无任何东西 |
\Z | 匹配必须出现在字符串结尾,允许后面出现换行符 |
\G | 匹配必须出现在上一个匹配结束的地方 |
零宽断言
与位置字符类似,零宽断言也是用于限定字符(不)出现在特定位置;不同的是其本身是看起来一个匹配模式却不产生输出,只做判定(不移动匹配定位指针),判定结果影响最终匹配结果。
零宽是指其不输出匹配结果,在最终匹配结果中不占字符宽度。零宽断言也被称为预查分组,正则表达式匹配时会先对其进行判断,只有在零宽断言判定为真时才进行匹配。
比如用于强密码验证的正则表达式:/^(?=^.{8,}$)(?=.*\d)(?=.*\W+)(?=.*[A-Z])(?=.*[a-z])(?!.*\n).*$/
,结果真正匹配的是/^.*$/
即包含任何字符的行,但只有当满足前面零宽断言时才会匹配:必须8位以上、必须出现数字、必须出现特殊符号、必须出现大写字母、必须出现小写字母且不得出现换行。
字符 | 说明 |
---|---|
(?=exp) | 零宽度正预测先行断言 断言自身位置后面一定能匹配表达式exp |
(?<=exp) | 零宽度正回顾后发断言 断言自身位置前面一定能匹配表达式exp |
(?!exp) | 零宽度负预测先行断言 断言自身位置后面不能匹配表达式exp |
(?<!exp) | 零宽度负回顾后发断言 断言自身位置前面不能匹配表达式exp |
模式修饰符
字符 | 说明 |
---|---|
/g | 全局匹配:(即一行上的每个出现,而不只是一行上的第一个出现) |
/s | 单行模式:把整个匹配串当作一行处理,匹配\n |
/m | 多行模式:不匹配\n |
/i | 忽略大小写 |
/x | 忽略空白符:允许模式中出现注释和空白符,增加可读性 |
/U | 非贪婪匹配 |
/o | ?仅赋值一次 |
/cg | ?全局匹配失败后,允许再次查找匹配串 |
流派对比
字符 | Basic RegEx | Extended RegEx | Perl RegEx | Python RegEx |
---|---|---|---|---|
., […], [^…], [ - ] | ✓ | ✓ | ✓ | ✓ |
\w, \W, \d, \D | ? | ✓ | ✓ | ✓ |
\s, \S, \cx, \xmn | ✓ | ✓? | ||
\f, \n, \r, \t, \v | \f\n\r\t | ✓ | ||
转义 \ |
✓ | ✓ | ✓ | ✓ |
分组 ( ) |
\( \) | ✓ | ✓ | ✓ |
特殊分组 (?: ), (?< >) |
✓ | ✓ | ||
或 | |
| | ✓ | ✓ | ✓ |
引用/捕获 \m $m |
✓ | ? | ✓ | ✓? |
行首尾 ^, $ |
✓ | ✓ | ✓ | ✓ |
单词边界 \b, \B |
<, \>, <\> | ✓ | ✓ | |
量词 ? * + {n} {n,} {n,m} |
?, *, \+ \{n\} \{n,\} \{n,m\} |
✓ | ✓ | ✓ |
模式修饰符 /g, /s , /m, /i, /x, /U |
? | ? | ✓ | ? |
零宽断言 (?=), (?<=), (?!), (?<!) |
? | ? | ✓ | ? |
?, +, |, {, }, (, )
等字符在Shell指令中有特殊含义,BERs遇到这些字符时须用\
进行转义
., *
等元字符被放在[ ]中,那么它们将变成一个普通字符,不具有特殊含义
Linux下常用文本工具支持情况
grep, egrep: 用于文本查找,处理时是按行处理的,默认返回包含“关健字”行的内容
sed, awk: 用于文本查找、增删替换等,前者按行处理,后者则既可按行也可按列处理
grep默认使用"BRE",跟参数-E
表示使用"ERE",跟参数-P
表示使用"PRE"
egrep默认使用"ERE",跟参数-P
表示使用"PRE"
sed默认使用"BRE",跟参数-r
表示使用"ERE",不支持"PRE"
awk默认使用"ERE",不支持"PRE"
注意:awk指令与一般正则表达式不同:^, $
匹配行首尾,awk指令中则是匹配字符串的首尾;., [^xyz]
匹配都不含\n
,awk指令中则包含换行符(如果要包含\n
,EREs可使用(^$)|(.),PREs可使用[.\n])
Python正则
re.search(pattern, string)
:扫描返回首次成功匹配,不成功返回Nonere.match(pattern, string)
:从字符串起始位置匹配,不成功返回Nonere.findall(pattern, string)
:查找返回全部匹配的子串列表或回空列表re.finditer(pattern, string)
:类似findall,但返回迭代器而非列表re.split(pattern, string)
:按照匹配的子串切分字符串re.sub(pattern, repl, string)
: 查找并替换re.compile(pattern)
:编译,返回re.Pattern
对象,直接使用各种方法
具体用法可参考Python官方文档。