学习:正则表达式
定义:
- 是指一个用来描述或者匹配一系列符合某个句法规则的字符串的单个字符串
- 通常被用来检索和/或替换那些符合某个模式的文本内容:
- 给定的字符串是否符合正则表达式的过滤逻辑(称作“匹配”);
- 可以通过正则表达式,从字符串中获取我们想要的特定部分
正则功能中还提供了对正则字符串的处理选项(模式修饰符)的安排,具体实现跟正则引擎有关,以下列出PHP的模式修饰符(紧接在正则字符串后,/后)
- i (PCRE_CASELESS):如果设置了这个修饰符,模式中的字母会进行大小写不敏感匹配。
- m (PCRE_MULTILINE):默认情况下,PCRE 认为目标字符串是由单行字符组成的(然而实际上它可能会包含多行), "行首"元字符 (^) 仅匹配字符串的开始位置, 而"行末"元字符 ($) 仅匹配字符串末尾, 或者最后的换行符(除非设置了 D 修饰符)。这个行为和 perl 相同。 当这个修饰符设置之后,“行首”和“行末”就会匹配目标字符串中任意换行符之前或之后,另外, 还分别匹配目标字符串的最开始和最末尾位置。这等同于 perl 的 /m 修饰符。如果目标字符串 中没有 "\n" 字符,或者模式中没有出现 ^ 或 $,设置这个修饰符不产生任何影响。
- s (PCRE_DOTALL):如果设置了这个修饰符,模式中的点号元字符匹配所有字符,包含换行符。如果没有这个 修饰符,点号不匹配换行符。这个修饰符等同于 perl 中的/s修饰符。 一个取反字符类比如 [^a] 总是匹配换行符,而不依赖于这个修饰符的设置。
- x (PCRE_EXTENDED):如果设置了这个修饰符,模式中的没有经过转义的或不在字符类中的空白数据字符总会被忽略, 并且位于一个未转义的字符类外部的#字符和下一个换行符之间的字符也被忽略。 这个修饰符 等同于 perl 中的 /x 修饰符,使被编译模式中可以包含注释。 注意:这仅用于数据字符。 空白字符 还是不能在模式的特殊字符序列中出现,比如序列 (?( 引入了一个条件子组(译注: 这种语法定义的 特殊字符序列中如果出现空白字符会导致编译错误。 比如(?(就会导致错误)。
- e (PREG_REPLACE_EVAL):如果这个修饰符设置了, preg_replace() 在进行了对替换字符串的 后向引用替换之后, 将替换后的字符串作为php 代码评估执行(eval 函数方式),并使用执行结果 作为实际参与替换的字符串。单引号、双引号、反斜线(\)和 NULL 字符在 后向引用替换时会被用反斜线转义.
- A (PCRE_ANCHORED):如果设置了这个修饰符,模式被强制为"锚定"模式,也就是说约束匹配使其仅从 目标字符串的开始位置搜索。这个效果同样可以使用适当的模式构造出来,并且 这也是 perl 种实现这种模式的唯一途径。
- D (PCRE_DOLLAR_ENDONLY):如果这个修饰符被设置,模式中的元字符美元符号仅仅匹配目标字符串的末尾。如果这个修饰符 没有设置,当字符串以一个换行符结尾时, 美元符号还会匹配该换行符(但不会匹配之前的任何换行符)。 如果设置了修饰符m,这个修饰符被忽略. 在 perl 中没有与此修饰符等同的修饰符。
- S:当一个模式需要多次使用的时候,为了得到匹配速度的提升,值得花费一些时间 对其进行一些额外的分析。如果设置了这个修饰符,这个额外的分析就会执行。当前, 这种对一个模式的分析仅仅适用于非锚定模式的匹配(即没有单独的固定开始字符)。
- U (PCRE_UNGREEDY):这个修饰符逆转了量词的"贪婪"模式。 使量词默认为非贪婪的,通过量词后紧跟? 的方式可以使其成为贪婪的。这和 perl 是不兼容的。 它同样可以使用 模式内修饰符设置(?U)进行设置, 或者在量词后以问号标记其非贪婪(比如.*?)。
- X (PCRE_EXTRA):这个修饰符打开了 PCRE 与 perl 不兼容的附件功能。模式中的任意反斜线后就 ingen 一个 没有特殊含义的字符都会导致一个错误,以此保留这些字符以保证向后兼容性。 默认情况下,在 perl 中,反斜线紧跟一个没有特殊含义的字符被认为是该字符的原文。 当前没有其他特性由这个修饰符控制。
- J (PCRE_INFO_JCHANGED):内部选项设置(?J)修改本地的PCRE_DUPNAMES选项。允许子组重名, (译注:只能通过内部选项设置,外部的 /J 设置会产生错误。)
- u (PCRE_UTF8):此修正符打开一个与 perl 不兼容的附加功能。 模式字符串被认为是utf-8的. 这个修饰符 从 unix 版php 4.1.0 或更高,win32版 php 4.2.3 开始可用。 php 4.3.5 开始检查模式的 utf-8 合法性。
不同语言实现的正则表达式规则不尽相同,不完全兼容,但大致规则一致,通过不同的元字符的组合形成正则字符串
限定符:匹配重复的方式
- * 重复零次或更多次
- + 重复一次或更多次
- ? 重复零次或一次
- {n} 重复n次
- {n,} 重复n次或更多次
- {n,m} 重复n到m次
匹配字符:多种字符匹配
- . 匹配除换行符以外的任意字符
- \w 匹配字母或数字或下划线或汉字
- \s 匹配任意的空白符
- \d 匹配数字
反义字符:对多种字符的反向匹对
- \W 匹配任意不是字母,数字,下划线,汉字的字符
- \S 匹配任意不是空白符的字符
- \D 匹配任意非数字的字符
定位字符:确定匹配规则的位置
- \b 匹配单词的开始或结束
- ^ 匹配字符串的开始
- $ 匹配字符串的结束
- \B 匹配不是单词开头或结束的位置
转义字符:对于本身有含义的字符串,或者不便书写的字符,采用在前面加“\”
- \r,\n 匹配回车和换行符
- \t 匹配制表符
- \\ 匹配“\”本身
- \^ 匹配^本身
- \$ 匹对$本身
- \. 匹对.本身
自定义的多种字符格式:通过“[]”中包含一系列字符,能够匹配其中任意一个字符
- [ab5@] 匹配 "a" 或 "b" 或 "5" 或 "@"
- [^abc] 匹配 "a","b","c" 之外的任意一个字符
- [f-k] 匹配 "f"~"k" 之间的任意一个字母
- [^A-F0-3] 匹配 "A"~"F","0"~"3" 之外的任意一个字符
- 分支条件字符:通过“|”进行多个条件的同时排列
- 分组字符:通过“()”通过设定字正则表达字符串,和其他字符组合,如限定符
- 反向引用:分组后边的部分,可以引用前面 "括号内的子匹配已经匹配到的字符串"。
- 引用方法是 "\" 加上一个数字。"\1" 引用第1对括号内匹配到的字符串,"\2" 引用第2对括号内匹配到的字符串……以此类推,如果一对括号内包含另一对括号,则外层的括号先排序号。换句话说,哪一对的左括号 "(" 在前,那这一对就先排序号。
其他元字符:
- \a 报警字符(打印它的效果是电脑嘀一声)
- \b 通常是单词分界位置,但如果在字符类里使用代表退格
- \t 制表符,Tab
- \r 回车
- \v 竖向制表符
- \f 换页符
- \n 换行符
- \e Escape
- \0nn ASCII代码中八进制代码为nn的字符
- \xnn ASCII代码中十六进制代码为nn的字符
- \unnnn Unicode代码中十六进制代码为nnnn的字符
- \cN ASCII控制字符。比如\cC代表Ctrl+C
高阶应用:
零宽断言:用于查找在某些内容(但并不包括这些内容)之前或之后的东西,也就是说它们像\b,^,$(定位字符)那样用于指定一个位置,这个位置应该满足一定的条件(即断言),因此它们也被称为零宽断言
- (?=exp) 匹配exp前面的位置
- (?<=exp) 匹配exp后面的位置
负向零宽断言:想要确保某个字符没有出现,但并不想去匹配它时,是零宽断言的反义
- (?!exp) 匹配后面跟的不是exp的位置
- (?<!exp) 匹配前面不是exp的位置
贪婪与懒惰:
- 当正则表达式中包含能接受重复的限定符时,通常的行为是(在使整个表达式能得到匹配的前提下)匹配尽可能多的字符。以这个表达式为例:a.*b,它将会匹配最长的以a开始,以b结束的字符串。如果用它来搜索aabab的话,它会匹配整个字符串aabab。这被称为贪婪匹配。
- 有时,我们更需要懒惰匹配,也就是匹配尽可能少的字符。前面给出的限定符都可以被转化为懒惰匹配模式,只要在它后面加上一个问号?。这样.*?就意味着匹配任意数量的重复,但是在能使整个匹配成功的前提下使用最少的重复。
现在看看懒惰版的例子吧:
- a.*?b匹配最短的,以a开始,以b结束的字符串。如果把它应用于aabab的话,它会匹配aab(第一到第三个字符)和ab(第四到第五个字符)。
- *? 重复任意次,但尽可能少重复
- +? 重复1次或更多次,但尽可能少重复
- ?? 重复0次或1次,但尽可能少重复
- {n,m}? 重复n到m次,但尽可能少重复
- {n,}? 重复n次以上,但尽可能少重复
- 是对限定符的补充。
递归匹配:把有嵌套的的成对括号或者成对标签进行匹对
各语言的实现不同且写法也不同
php中的写法:
- "(?R)" ,这个符号可以表示: 正则模式本身
- “(?index)”,这个符号表示序号引用,引用之前的子表达式,
.net称为平衡组:
- (?'group') 把捕获的内容命名为group,并压入堆栈(Stack)
- (?'-group') 从堆栈上弹出最后压入堆栈的名为group的捕获内容,如果堆栈本来为空,则本分组的匹配失败
- (?(group)yes|no) 如果堆栈上存在以名为group的捕获内容的话,继续匹配yes部分的表达式,否则继续匹配no部分
- (?!) 零宽负向先行断言,由于没有后缀表达式,试图匹配总是失败
例子:
<div[^>]*>[^<>]*(((?'Open'<div[^>]*>)[^<>]*)+((?'-Open'</div>)[^<>]*)+)*(?(Open)(?!))</div>