正则指引
TRANSCRIPT
正则指引 RegExp Guide
shengxuanwei 2014-02-20
什么是正则?
• 正则表达式是⼀一种描述字符串结构模式的形式化表达⽅方法
• 使⽤用正则表达式,将会以⼀一个全新的⾓角度看待⽂文本,它们已经不仅仅是⼀一串字符流,⽽而且拥有⼀一定的结构,可以对其进⾏行分解、提取和加⼯工(验证,查找,替换)
学习曲线(堪⽐比vi)
• Some people, when confronted with a problem, think,“I know, I'll use regular expressions.” Now they have two problems.
正则是什么?• 唬⼈人的正则
• 合法的IP地址
(?s)/\*(?:(?!\*/)[*$ _/+\\-])*(.*?)[*$ _/+\\-]*?\*/
((2[0-4]\d|25[0-5]|[01]?\d\d?)\.){3}(2[0-4]\d|25[0-5]|[01]?\d\d?
怎么学习正则?• ⽂文本,观察⽂文本,描述⽂文本特征和边界条件
• 语法,学习正则模式,多尝试,多练习
• 基础特性(30min掌握)
• ⾼高级特性(30min了解)
• 原理,学习匹配原理,提⾼高匹配效率
《精通正则表达式》
• 权威经典
• ⽆无出其右
元字符(meta-characters)
• 特殊字符,提供了强⼤大的描述能⼒力
• 与之相对应的是⽂文字(literal)
. 匹配除换⾏行符以外的任意字符\w 匹配字⺟母或数字或下划线或汉字\s 匹配任意的空⽩白符\d 匹配数字\b 匹配单词的开始或结束^ 匹配字符串的开始$ 匹配字符串的结束
字符转义(escape)
• 转义符 \!
• 取消字符的特殊意义
• 例如,\. 匹配字符 .
重复量词(Quantifiers)
• 限定了所作⽤用元素的匹配次数
* 重复零次或更多次,等效于{0,}
+ 重复⼀一次或更多次,等效于{1,}
? 重复零次或⼀一次,等效于{0,1}
{n} 重复n次
{n,} 重复n次或更多次
{n,m} 重复n到m次
字符组(Character Classes)
• 匹配若干字符之⼀一,[aeiou]
• 字符组内部,-被作为元字符,表⽰示⼀一个范围,[a-z],放在字符组内最前部,表⽰示-字符
• .?在字符组不作为元字符,[.?!]
字符组(Character Classes)
• 缩略表⽰示法,\w,\d,\s
• 排除型字符组,[^0-9],\W,\D,\S
\w 匹配任意字⺟母,数字,下划线,汉字的字符,等效于[a-zA-Z0-9_]
\d 匹配任意数字的字符,等效于[0-9]
\s 匹配任意空⽩白符的字符,等效于∙\f\n\r\t\v
\W 匹配任意不是字⺟母,数字,下划线,汉字的字符
\D 匹配任意⾮非数字的字符
\S 匹配任意不是空⽩白符的字符
[^x] 匹配除了x以外的任意字符
[^aeiou] 匹配除了aeiou这⼏几个字⺟母以外的任意字符
多选结构(Alternation)
• 元字符,|,表⽰示或,The|the|THE
• ⽤用()限制多选范围,baidu.(com|org|net)
• 注意各个条件间的顺序,从左往右测试
⼦子表达式(subexpression)
• 整个表达式的⼀一部分,元字符 ()
• 量词作⽤用的对象是它们之前紧邻的⼦子表达式,(abc)*
• 分组(Grouping),捕获(Capturing)
捕获组(Capturing Group )
• 捕获组是把⼦子表达式匹配的内容,保存到内存中以数字编号或显式命名的组⾥里,⽅方便后⾯面引⽤用
• 引⽤用既可以是在正则表达式内部,也可以是在正则表达式外部
• 普通捕获组,(Expression)
• 命名捕获组,(?<name>Expression)
反向引⽤用(backreference)
• 反向引⽤用(\num )⽤用于重复搜索前⾯面某个分组匹配的⽂文本
• 分组规则,从左向右,以分组的左括号为标志,第⼀一个出现的分组的组号为1,第⼆二个为2,以此类推
• (\w)\1,(\w)表⽰示⼀一个分组,\1表⽰示第⼀一分组
基础特性就这么多
⾮非捕获组(Noncapturing Group)
• 仅⽤用于分组的括号,(?:Expression)
• ⽤用来规定多选结构或者量词作⽤用对象,不⽤用来提取⽂文本
• 能够把复杂的表达式变得清晰,减少引⽤用和分组序号
• 提⾼高匹配效率
贪婪与懒惰(Greedy and Lazy)
• 当正则表达式中包含能接受重复的限定符时,通常的⾏行为是(在使整个表达式能得到匹配的前提下)匹配尽可能多的字符
• a.*b匹配abcb,得到abcb
• 懒惰匹配,也就是重复量词匹配尽可能少的字符
• 在量词后⾯面加?,忽略优先量词
• a.*?b匹配abcb,得到ab
*? 重复任意次,但尽可能少重复
+? 重复1次或更多次,但尽可能少重复?? 重复0次或1次,但尽可能少重复
{n,m}? 重复n到m次,但尽可能少重复{n,}? 重复n次以上,但尽可能少重复
环视(Lookaround)• 也叫零宽断⾔言(zero-width assertion)
• 环视结构不匹配任何字符,只匹配⽂文本中的特定位置,与\b,^,$相似
肯定顺序环视 (?=Expression) 匹配Expression前⾯面的位置
肯定逆序环视 (?<=Expression) 匹配Expression后⾯面的位置
否定顺序环视 (?!Expression) 匹配后⾯面跟的不是Expression的位置
否定逆序环视 (?<!Expression) 匹配前⾯面不是Expression的位置
肯定顺序环视(Positive Lookahead)
• 肯定顺序环视(?=Expression),从左往右检查⽂文本,尝试匹配⼦子表达式,如果能够匹配,就返回成功信息和位置。
• (?=\d)表⽰示如果当前位置的右边字符是数字则匹配成功
• AA(?=BB)和(?=AABB)AA,匹配AABBCC,效果⼀一致
否定顺序环视(Negative Lookahead)
• <I>((?!<I>).)*?<\/I>
• <I>x<I>abc</I>x</I>
条件判断(?if then |else) Conditionals
• if 部分的测试如果为真(被匹配),尝试使⽤用then,否则使⽤用else(else部分可以不出现)
• if条件部分常常为环视
• (?(?<=NUM:)\d+|\w+)
• 在' NUM: '后的位置匹配\d+,在其他位置匹配\w+
固化分组(Atomic Grouping)
• 固化分组抑制回溯的发⽣生
• ⼀一旦⼦子表达式匹配之后,匹配的内容将固定下来,除⾮非整个固化分组都被启⽤用,在外部回溯中重新应⽤用
• 前提是理解正则引擎匹配原理
• A(?>[0-9]+)9,匹配A789失败
占有优先量词(Possessive Quantifiers)
• 类似于固化分组的模式,在匹配的过程中不会“交还”中间的结果
• 就功能上⽽而⾔言可以使⽤用固化分组进⾏行模拟
• .++和(?>.+)
• PCRE中对占有优先量词的实现做了⼀一些优化
模式修饰符(Mode Modifier)
• 切换正则表达式模式和匹配模式
i 不区分⼤大⼩小写的匹配模式
x宽松排列和注释模式,忽略字符组外部的所有空⽩白,#和换⾏行符之间的内容视为注释
s 单⾏行模式,.能够匹配换⾏行符\n
m 增强的⾏行锚点模式,\A,\Z
(?i)Expression(?-i)!
(?i:Expresssion)!
/Expression/i
好了,⾼高级特性也差不多全了
引擎分类(Engine Types)
名称 ⼯工具 特点
DFA awk, egrep, MySQL 速度快、功能弱
NFAJava, NET, PCRE, Perl, PHP, Python, Ruby, sed,
vi,emacs速度慢、功能强
• 构建正则表达式的⽅方式决定了某个正则表达式能否匹配⼀一个特定字符串,在何处匹配,以及匹配成功或报告失败的速度。
DFA,确定性有穷⾃自动机 (Deterministic Finite Automation)
• ⽂文本主导
• 状态:(aba|abb|abc) 匹配 abcd
• 优势:速度快
• 弊端:不⽀支持括号捕获,不⽀支持环视,不⽀支持“固化分组”
NFA,⾮非确定性有穷⾃自动机 (Nondeterministic Finite Automation)
• 表达式主导
• 回溯(最核⼼心)
• 最左最⻓长匹配,匹配成功跳出,可能并不是最⻓长匹配
• a(b)?(b[cd])* 匹配 abcabdbd
匹配过程1. 正则表达式编译(检查语法,并编译为内部形式)
2. 传动开始(定位⾄至字符串起始位置)
3. 元素检测(相连元素、量词修饰符、控制权)
4. 寻找匹配结果(NFA找到后锁定,报告匹配成功;DFA继续下⼀一个,找最⻓长结果)
5. 传动装置的驱动过程(没匹配成功,从下⼀一个字符开始)
6. 匹配彻底失败(所有字符尝试完毕,返回彻底失败)
回溯(Backtracking)
• 依次处理各个⼦子表达式或组成元素,遇到需要在多个可能成功的位置中进⾏行选择时,它会选择其⼀一,同时记住另⼀一个,以防匹配失败后⽤用另⼀一个分⽀支继续尝试
• 作出选择的情形包括:量词和多选结构
• ⾯面包屑
• a(b)?(b[cd])*
备⽤用状态(Saved States)
• ⾯面包屑相当于备⽤用状态
• 在需要的时候,匹配可以从这⾥里重新开始尝试。
• 备⽤用状态保存了两个位置:
• 正则表达式中的位置
• 未尝试的分⽀支在字符串中的位置
匹配优先量词原理• hel*o 匹配 hello
• he匹配完后轮到l*
hel*o hello
• 备选状态
hel*o hello
匹配优先量词原理
• l*匹配之后
hel*o hello
• 备选状态
hel*o hello
忽略优先量词原理• hel*?o 匹配 hello
• he匹配完后轮到l*?
hel*?o hello
• 备选状态
hel*?o hello
• l*?忽略优先直接跳过hel*?o hello
占有优先量词原理
• 占有优先量词、固化分组⾥里的⼦子表达式,舍弃备选分⽀支
• (?>.*?)能够匹配什么?
正则优化建议• 尽量使⽤用^,$锚点
• .*通常不是最合适的选择
• 字符组/字符识别优化
• 使⽤用⾮非捕获型括号
• 从量词中提取必须元素
• 提取多选结构开头的必须元素
• 将最可能匹配的分⽀支放到多选结构的最前头
• 使⽤用固化分组,放弃备⽤用状态,提⾼高效率
• 能不⽤用正则就不⽤用
js-regexp
• https://github.com/kaustubh-karkare/js-regexp
• Pattern,Node,Token,State
• 最核⼼心的设计是扫描正则表达式,根据不同类型Node构建Token,形成Node结构树
• State在匹配过程中记录两个位置,正则表达式中的位置和未尝试的分⽀支在字符串中的位置
构建树结构
• a(b)?(b[cd])
• Node.And
• Node.Char(a)
• Node.Loop{0,1}
• Node.Char(b)
• Node.And(capturing group)
• Node.Char(b)
• Node.Or
• Node.Char(c)
• Node.Char(d)
“编写正则表达式时,⼀一半时间花在按预期获得成功的匹配,另⼀一半时间⽤用来考虑如何忽略那些不
符合要求的⽂文本。”
“要想在复杂性和完整性之间求得平衡,关键是了解你所⾯面对的⽂文本。”
“正则不是死板的教条,它更像是艺术。”
参考资料 • 《精通正则表达式(第三版)》,Jeffrey.E.F.Friedl
• 《正则指引》,余晟著
• rail-road diagrams,http://www.regexper.com/
• Regex Golf,http://regex.alf.nu/
• Regex Tester,http://regexpal.com/
• RegexBuddy,http://www.regexbuddy.com/
• 《正则表达式30分钟⼊入⻔门教程》,http://deerchao.net/tutorials/regex/regex.htm
• 《笔记:如何写出⾼高效率的正则表达式》
• Regular Expressions Cheat Sheet,http://www.cheatography.com/davechild/cheat-sheets/regular-expressions/
Q & A