正则表达式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-MEnter
\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
2
3
4
5
6
7
8
嵌入条件分组(?(condition)true_sub-regex|false_sub-regex)
分支复位分组(?|sub-regex)
表达式引用分组(?R)或(?num)
平衡分组(?<-name>sub-regex)
固化分组(即原子分组)(?>sub-regex)
不匹配任何东西
内联修饰选项与取消内联修饰选项分组(?modifier-modifier)
注释分组(?#comment)

分组( )也被称为捕获组,因为( )出现会触发正则表达式引擎的“捕获”:将匹配到的字符串(而非模式本身)保存在特殊缓存中,除非使用(?:)禁止捕获。多个括号会对应有多个捕获组。

我们可以通过反向引用\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)会匹配到dxxxddxxd,(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
转义 \
分组 ( ) \( \)
特殊分组(?: ), (?< >)
| &#124;
引用/捕获 \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):扫描返回首次成功匹配,不成功返回None
  • re.match(pattern, string):从字符串起始位置匹配,不成功返回None
  • re.findall(pattern, string):查找返回全部匹配的子串列表或回空列表
  • re.finditer(pattern, string):类似findall,但返回迭代器而非列表
  • re.split(pattern, string):按照匹配的子串切分字符串
  • re.sub(pattern, repl, string): 查找并替换
  • re.compile(pattern):编译,返回re.Pattern对象,直接使用各种方法
    具体用法可参考Python官方文档