`
yuanlanxiaup
  • 浏览: 852694 次
文章分类
社区版块
存档分类
最新评论

php pcre正则表达式完全教程----pcre官方文档

 
阅读更多

翻译有不正确的地方, 恳请指正......本翻译过段时间录入php官方文档
translator: slefimpr
blog: http://blog.csdn.net/lgg201
mail: lgg860911@yahoo.com.cn

PCRE

简介

PCRE 扩展的正则表达式会有一个每个线程都可用的全局缓存用来缓存编译后的正则表达式.

PCREphp4.2.0中是默认启用的, 可以通过—without-pcre-regex禁用. php 5.3.0之后, 这个扩展不能被禁用. 但是仍然可以使用—with-pcre-regex=DIR来实用一个外部的pcre库进行编译

配置

pcre.backtrack_limit: 默认100000, pcre回溯限制

pcre.recursion_limit: 默认100000, pcre的递归限制. 需要注意的是, 如果将这个值设置为一个很大的数字, 你可能会消耗掉所有的进程可用堆栈, 并且最终导致php崩溃(直到到达系统限制的堆栈大小)

预定义常量

PREG_PATTERN_ORDER

结果按照规则排序, 仅用于preg_match_all, $matches[0]是完整规则的匹配结果, $matches[1]是第一个子组匹配的结果

PREG_SET_ORDER:

结果按照集合排序, 仅用于preg_match_all, $matches[0]保存第一次匹配结果的所有结果(包含子组)信息, $matches[1]保存第二次的结果信息

PREG_OFFSET_CAPTURE

在捕获的结果中, 不仅包含结果子串, 还会包含子串在原字符串中的位置.

PREG_SPLIT_NO_EMPTY

告知preg_split仅返回非空的部分

PREG_SPLIT_DELIM_CAPTURE

告知preg_split同时捕获规则中的括号表达式匹配到的内容

PREG_SPLIT_OFFSET_CAPTURE

如果设置了这个标记, 每次匹配得到的结果的offset位置将会被同时返回.

PREG_NO_ERROR

如果preg_last_error调用后返回此值, 则表明正则没有错误

PREG_INTERNAL_ERROR

如果preg_last_error调用后返回此值, 则表明发生了PCRE内部错误

PREG_BACKTRACK_LIMIT_ERROR

如果回溯次数超过预设的值, preg_last_error函数返回此值

PREG_RECURSION_LIMIT_ERROR

如果递归次数超过预设的值, preg_last_error函数返回此值

PREG_BAD_UTF8_ERROR

如果preg的最后错误是由于异常的utf-8数据(仅在运行在utf-8模式正则表达式情况下)导致的, 那么preg_last_error返回此值

PREG_BAD_UTF8_OFFSET_ERROR

如果偏移量与合法的utf-8代码(仅在运行在utf-8模式正则表达式情况下)的开始点不匹配, preg_last_error的调用返回此值

PCRE_VERSION

返回pcre版本号和发布日期.

模式修饰符

i PCRE_CLASSES

大小写不敏感的修饰符

m PCRE_MULTILINE

多行匹配, 默认情况下, PCRE认为目标字符串是一组单行字符组成的(然而实际上它可能会包含多行). 元字符^仅匹配字符串的开始位置, 而元字符$则仅匹配字符串的末尾, 或者新行之前(除非设置了D修饰符). 这个修饰符和perl中工作一直, 使用后, ^$会匹配每一行的开始和结尾

如果目标字符串中没有/n字符或者模式中没有出现^$, 则设置此修饰符是没有任何影响的.

s PCRE_DOTALL

用来增强字符类.(点号)的功能, 默认情况下, 点号匹配任意字符, 但不包括换行符及以后内容. 使用此修饰符后, 可以匹配任意字符包括换行符

x PCRE_EXTENDED

如果设置了这个修饰符, 模式中的空白字符, 除非被转义或者在一个字符类中, 否则都被忽略. 在一个未转义的#之后直到下一个换行符之间的内容都会被正则表达式引擎忽略以方便添加注释.

e PREG_REPLACE_EVAL

使用此修饰符后, preg_replace$replacement参数除了可以使用正常的反向引用来取得匹配值, 还可以在其中书写eval语法允许的字符串进行求值, 并将返回结果用于最终的替换.

A PCRE_ANCHORED

如果设置了此修饰符, 模式被强制成为”anchored”(锚点), 也就是说约束使其始终从字符串的嘴前面开始搜索. 这个影响也可以通过构造适当的规则来实现(perl中只能这样实现)

D PCRE_DOLLAR_ENDONLY

如果设置了这个修饰符, $会匹配目标字符串的结尾之前, 而如果没有设置这个修饰符, 如果目标字符串最后一个字符时换行符, $就会匹配该换行符之前而不是字符串结尾之前.

如果设置了m修饰符, 则这个修饰符会被忽略. 这一点和perl不一致

S

当一个模式需要多次使用的时候, 我们为了获得更高的匹配效率, 值得花费一些时间对其进行分析. 如果设置了这个修饰符, 将会进行这个额外的分析. 当前, 这种对一个模式的分析仅仅适用于非锚点的匹配(即没有一个单一固定的开始字符)

U PCRE_UNGREEDY

这个修饰符逆转了贪婪的模式, 如果没有这个设置, 默认情况下的正则匹配时贪婪的, 量词后增加了?的模式是非贪婪的, 但是如果设置了这个修饰符, 则刚好相反.

这个规则与perl是不兼容的.

也可以在模式中使用?U来达到同样的效果

X PCRE_EXTRA

这个修饰符启用了一个PCRE中与perl不兼容的额外功能. 任意反斜线后面跟一个没有特殊含义的字符会导致一个错误, 以此来保留这些组合以备后期扩展.

默认情况下, perl一样, 反斜线后跟一个没有特殊含义的字符会以该字符原意解释.

当前没有任何其他特性受此修饰符控制

J PCRE_INFO_JCHANGED

与内部选项?J相同, 用来改变本地PCRE_DUPNAMES选项. 允许子组有重复的名字

u PCRE8

这个修饰符打开一个PCRE中与perl不兼容的额外功能. 模式字符串被认为是UTF-8.

perl的不同之处

默认情况下,使用C库函数isspace()判定一个任意字符是否是空白字符, 尽管可以实用字符类表替代编译PCRE. 通常isspace()匹配空格, 换页, 换行, 回车, 水平制表符和垂直制表符. perl5不再将垂直制表符包括到空白字符集中. /v这个转移实际上在很长时间都没有得到perl文档的承认. 然而, 这个字符自身被认为是一个空白字符至少是在5.002之上. 5.0045.005它不和/s匹配.

PCRE 不允许前瞻断言的量词修饰, perl允许这样做, 但是这并不是你想象的那样.例如, (?!a){3}并不意味着断言接下来三个字符不是a, 而是断言下一个字符不是a并进行3.

子模式的捕获发生在负向前瞻内部时会被计算, 但是在偏移向量中病没有设置它们的条目. perl从断言失败之前匹配得到的这些模式匹配结果中设置了它的数值变量 (因此是成功的), 但这也仅在负向前瞻断言只有一个分支的时候.

尽管目标字符串中支持二进制0字符, 但是他们在模式字符串中是不允许的, 因为他们是通过普通的C字符串传递的, C字符串以0字符结束. 转义序列”/x00”可以在模式中用于表示二进制0字符.

不支持下面的perl转义序列: /I, /u, /L, /U. 实际上这些都是通过perl一般的字符串处理来实现的, 而不是模式匹配引擎的一部分.

不支持perl/G断言, 因为它与单模式匹配没有关系.

很显然, pcre不支持(?{代码})(??{代码})的构造. 然而, 它支持递归模式

perl 5.005_02中当设置为捕获字符串的模式中有部分重复的时候会有一些古怪的现象发生, 比如: /^(a(b)?)+$/捕获aba的时候, $2会被设置为b, 然而, 如果把模式修改为/^(aa(bb)?)+$/的时候, aabbaa去匹配, $2将不会得到匹配结果. 如果将模式修改为/^(aa(b(b))?)+$/的时候, $2$3又都能够匹配到结果. perl 5.004$2在这几种情况下都能够得到匹配结果, 并且PCRE中夜市这样. 如果未来perl修改为一致的那就不同了, PCRE可能接下来会修改.

还有一个没有解决的差异是perl 5.005_02, 模式/^(a)?(?(1)a|b)+$/会匹配字符串”a”, pcre中不会. 然而, perlpcre/^(a)?a/匹配”a”都会得到相同的结果, $1都未被设置.

PCRE提供了一些对perl正则表达式的扩展

<!--[if !supportLists]-->1. <!--[endif]-->虽然后瞻断言要求必须匹配固定长度的字符串, 然而后瞻断言的每个可选分支还是可以实用不同长度的字符串的, perl 5.005中要求它们必须拥有同样的长度

<!--[if !supportLists]-->2. <!--[endif]-->如果PCRE_DOLLAR_ENDONLY设置了并且PCRE_MULTILINE没有设置, $元字符仅匹配字符末尾之前

<!--[if !supportLists]-->3. <!--[endif]-->如果PCRE_EXTRA设置了, 反斜线紧跟一个没有特殊含义的字符将会导致错误

<!--[if !supportLists]-->4. <!--[endif]-->如果PCRE_UNGREEDY设置了, 贪婪模式控制被逆转, : 默认是非贪婪模式, 在量词后增加?表明是贪婪模式.

POSIX正则的区别

PCRE函数需要的模式以分隔符闭合的包含.

不像POSIX, PCRE扩展没有专门的函数用来区分大小写敏感. 取而代之的是使用i这个模式修饰符来达到相同的效果. 其他模式修饰符也可以用来改变匹配策略.

POSIX函数从最左面寻找最长的匹配, 但是PCRE在查找到第一个合法匹配后就结束. 如果字符串没有匹配到结果, 那么两者是没有差别的, 但是如果匹配到结果, 两者就会在结果和速度上都有差别. 为了说明这个不同之处, 考虑Jeffrey Friedl著的精通正则表达式一书中的例子. 使用one(self)?(selfsufficient)?这个模式来匹配oneselfsufficient这个字符串, PCRE将会产生oneself这个结果, 但是POSIX将会匹配到完整的字符串oneselfsufficient. 两者都从原始字符串中匹配到了结果, 但是POSIX需要的是最长的结果.

函数列表

mixed preg_replace(mixed $pattern, mixed $replacement, mixed $subject[, int $limit = -1[, int &$count]])

执行一个正则表达式的搜索和替换. $subject中搜索与$pattern匹配的结果并使用$replacement替换它们.

$pattern

要搜索的模式. 可以是字符串或者一个字符串数组.

e修饰符可以使preg_replace()函数在进行了参数替换(后向引用)$replacement参数被当做php代码来执行. 请确保$replacement是合法的php代码, 否则会在包含preg_replace()的行上引发一个解析错误

$replacement

用于替换的字符串或字符串数组. 如果这个参数是字符串, 并且$pattern参数是一个数组, 所有的模式都是用这个字符串描述的规则进行替换. 如果$pattern$replacement都是数组, 每个模式是用$replacement中对应的字符串进行替换. 如果$replacement中的元素比$pattern中的元素少, 那么$pattern中多出来的模式是用空字符串进行替换.

$replacement可以使用//n$n进行后向引用, 后者是首选的语法. 每个这样的后向引用语法会被替换为模式捕获到的第n个子组的匹配结果文本. n可以是0-99之间的数字. //0或者$0后向引用的是整个模式匹配到的结果文本. 捕获子组的序列(这里的n)指的是代表子组的圆括号中的左括号在模式中出现的顺序(即第一个左括号代表的子组n=1 第二个n=2 依此类推), 如果想要在$replacement中使用反斜线, 需要对其进行转义(PHP字符串”////”)

$replacement规则使用后向引用工作时, 并且后向引用后面需要立即使用一个数值, 比如”//11”这个时候, 正则表达式引擎是没有办法判定你后向引用的子组是”//1”而不是”//11”, 这种情况下使用语法/${1}1来解决, {}将后向引用子组序号1和原文字符串1进行隔离.

当使用e修饰符时, 这个函数转义字符串中的一些字符(: ’, “, /和空字符), 替换后向引用, 这些完成之后, 确保后向引用替换没有导致语法错误, 然后会将$replacement作为php代码执行, 并将执行结果作为最终用来替换的字符串.

$subject

用于搜索和替换的源字符串或字符串数组.

如果$subject是一个数组, 会搜索和替换每一个$subject中的元素, 并且同样返回一个数组

$limit

每个模式在每个$subject字符串中最大可以被替换的次数. 默认-1(不限制)

$count

一个引用方式的参数, 如果指定, 该参数最终被填充为进行替换的次数.

返回

如果$subject是一个数组, 返回数组, 否则返回字符串.

如果找到匹配, 返回替换后新的$subject. 其他情况下$subject会无变化返回. 当发生错误时返回NULL.

mixed preg_filter(mixed $pattern, mixed $replacement, mixed $subject[, int $limit = -1[, int &$count]])

这个函数等同于preg_replace, 除了它仅仅返回匹配结果. 更多关于此函数如何工作的信息, 请查看preg_replace文档

如果$subject参数是一个数组, 返回一个数组描述的匹配结果, 否则返回string

如果没有匹配到任何结果或发生了错误, $subject是数组时返回空数组, 否则返回NULL

array preg_grep(string $pattern, array $input[, int $flags = 0])

$input中的每一个元素使用$pattern进行测试, 返回匹配的元素组成的数组. 如果$flags的值为PREG_GREP_INVERT, 那么将会返回不匹配的元素组成的数组. 返回的数组中元素的索引会被保留.

int preg_last_error(void)

返回最后一次pcre正则表达式执行的错误代码.

PREG_NO_ERROR/PREG_INTERNAL_ERROR/PREG_BACKTRACK_LIMIT_ERROR/PREG_RECURSION_LIMIT_ERROR/PREG_BAD_UTF8_ERROR/PREG_BAD_UTF8_OFFSET_ERROR等错误代码的意义详见pcre预定义常量部分.

int preg_match_all(string $pattern, string $subject, array &$matches[, int $flags[, int $offset]])

$subject中搜索所有与$pattern给定正则表达式匹配的结果, 并且将匹配到的结果按照$flags指定的顺序放入$matches

在第一个匹配发现后, 子序列继续搜索直到最后一个匹配.

$pattern

用于搜索的模式

$subject

输入字符串

$matches

保存匹配结果的多维数组, 顺序依赖于$flags参数

$flags

可以是以下值的组合.(注意, 这并不是说PREG_PATTERN_ORDERPREG_SET_ORDER可以同时使用)

如果没有给定任何flag, 默认启用的是PREG_PATTERN_ORDER

译注: 一个模式可能在字符串中发现多个匹配, 一个模式也会有多个子组, 这里的排序flag用来指明第一维表示子组还是多次匹配

PREG_PATTERN_ORDER: $matches[0]中包含第一个子组中所有匹配到的结果, $matches[1]中包含第二个子组中所有匹配到的结果.

PREG_SET_ORDER: $matches[0]中包含第一次匹配得到的结果(包含所有子组), $matches[1]包含第二次匹配得到的结果(包含所有子组)

PREG_OFFSET_CAPTURE: 如果传递了这个flag, 每一个匹配的字符串在返回时都会包含它在原始字符串中的偏移量. 注意: 这会改变$matches中最终输出的匹配结果数组元素, 原来是二维数组, 且第二维值是匹配到的字符串, 使用此flag, 第二维会成为一个数值索引的数组, 其中第一个元素是匹配得到的字符串, 第二个元素是该匹配结果在原始字符串($subject)中的偏移量

$offset

模式$pattern从字符串($subject)的什么位置开始搜索. 这个可选参数用来控制从指定位置开始搜索(单位是字节)

注意: 实用$offset参数和传递实用substr($subject, $offset)截取后的$subjectpreg_match_all()函数中是不一样的, 因为使用了$offset之后, $pattern依然可以使用^, $元字符进行首尾判断, 并且可以使用(?<=xx)进行前瞻断言.

返回

返回完成模式成功匹配的次数(可能是0), 当发生错误时返回FALSE

changelog

php5.2.2, $pattern开始可以使用(?<name>), (?’name’)以及(?P<name>)来进行子组命名, 之前的版本中只接受(?P<name>)语法.

int preg_match(string $pattern, string $subject[, array &$matches[, int $flags[, int $offset]]])

使用$pattern搜索$subject的一个匹配, 如果给定了$matches参数, 则该参数用来保存搜索结果.

除了$flags参数不能接受PCRE_PATTERN_ORDERPCRE_SET_ORDER来决定顺序外, 其余参数用法和preg_match_all()一致

返回$pattern匹配的次数. 它可能是0(没有匹配)或者1(因为preg_match()在搜索到一个匹配之后就会停止)

当发生错误的时候, 返回false.

注意: 如果你仅仅想判断一个字符串是否包含在另外一个字符串中, 不要使用preg_match, 请使用strposstrstr

string preg_quote(string $str[, string $delimiter = null])

$str中的每一个正则表达式语法定义的特殊字符进行转义. 这通常用于你有一个运行时字符串需要用作正则表达式进行匹配而它又包含特殊的正则表达式字符的时候.

特殊的正则表达式字符包括: . / + * ? [ ^ ] $ ( ) { } = ! < > | : -

可选的$delimiter参数用来指定额外的需要进行转义的字符, 这通常用于对你的正则表达式分隔符进行转义, 比如通常用作正则表达式分隔符的/

mixed preg_replace_callback(mixed $pattern, callback $callback, mixed $subject[, int $limit = -1[, int &$count]])

这个函数和preg_replace()的功能相同, 不同点在于$replacement使用了一个回调函数进行自定义的构造, 每一个获取到的对$pattern的完整匹配会对应一次替换, 也就是对$callback的调用, 调用时$callback接受到的参数是所有的子组, 其中第一个参数是第0个子组, 也就是对$pattern的完整匹配. 最终用作替换的字符串是$callback的返回值.

其他参数与preg_replace相同.

array preg_split(string $pattern, string $subject[, int $limit = -1[, int $flags = 0]])

用给定的$pattern分隔字符串形成数组返回.

$limit

用来限制最终返回的数组的最大长度, 默认-1表示不限制, 0也表示不限制.

$flags

可以是以下值的组合

PREG_SPLIT_NO_EMPTY: 如果此项被设置, 最终返回的数组仅包括分隔之后非空的部分

PREG_SPLIT_DELIM_CAPTURE: 如果此项设置, $pattern中的括号表达式奖会被捕获并作为返回

PREG_SPLIT_OFFSET_CAPTURE: 如果此项设置, 得到的分隔结果中不仅包含分隔后的字符串, 还会包括该子串在原始字符串中的偏移量. (译注: 假设使用”/ab/”分隔字符串”helloabworld”原来返回array(“hello”, “world”)), 那么使用此标记后返回array(array(“hello”, 0), array(“world”, 7))

注意: 如非必须, 不要使用preg_split, 你可以使用explode()str_split()替代.

pcre/posix函数总结

ereg_replace, eregi_replace, preg_replace, preg_replace_callback, preg_filter用来做正则替换

ereg, eregi, preg_match, preg_match_all用来做正则匹配

split, spliti, preg_split用来做正则分割

preg_grep用来做正则查找.

模式语法

介绍

下面描述的是PCRE支持的正则表达式语法和语义. 正则表达式同样在perl文档或其他一些书籍中有所描述, 其中有非常丰富的示例. Jeffrey Friedl精通正则表达式一书非常详细的讨论了这些内容. 这里的描述目的仅作为参考手册.

一个正则表达式的模式从左至右不断匹配一个目标字符串. 大多数字符自身就代表一个模式, 并且匹配目标字符串中对应的字符. 作为一个简单的例子The quick brown fox匹配在目标字符串中与其相同的部分.

分隔符

当使用PCRE函数的时候, 它要求模式使用闭合的分隔符包裹. 分隔符可以是任意的非字母数字, 非反斜线, 非空的字符.

经常用作分隔符的符号有/, #, ~.

如果在模式中需要使用分隔符自身, 就需要通过在前面追加一个反斜线对其进行转义. 如果分隔符经常需要在模式中出现, 那么一个好的选择就是选用另外的字符当做分隔符.

preg_quote函数可以用于转义字符串中被用作正则表达式特殊字符的字符, 并且它的第二个参数可以用于指定分隔符同样对其进行转义.

除了上面提到的分隔符, 还可以使用括号样式的分隔符, 左括号和右括号分别位于模式字符串的开始和结束位置即可(大括号{}, 中括号[],小括号()都可以)

你可以在结束的分隔符后面增加模式修饰符.

元字符

正则表达式的威力在于它在模式中拥有选择和重复的能力. 这些编码在模式中被称作元字符, 也就是说这些字符不再单纯代表它们自己, 而是赋予了特别的含义.

有两种不同的元字符, 一种是可以在方括号外任何地方使用的, 另外一种是在方括号内使用的.

方括号外的元字符

/ 通常用于字符转义

^ 代表目标字符串的开始位置(在多行模式下为行首)

$ 代表目标字符串的末尾位置(在多行模式下为行尾)

. 除了换行符外的任意字符(这是默认情况, 其他意义参见模式修饰符s)

[ 字符类定义的开始标记

] 字符类定义的结束标记

| 开始一个可选分支

( 子组开始标记

) 子组结束标记

? 作为()量词(直接位于字符类/子组后面), 代表0个或1. 位于量词后面, 逆转贪婪模式

* 0个或任意多个

+ 1个或任意多个

{ 自定义量词开始标记

} 自定义量词结束标记

在中括号内的元字符

/ 用于转义字符

^ 仅当作为中括号第一个字符时, 表明该字符类取反, 即匹配不在其中罗列的字符

- 声明字符范围

] 自定义字符类结束

反斜线

反斜线有多种用法. 首先, 如果它后面紧跟一个非数字字母的字符, 表明取消任何该字符的特殊含义, 进行原文匹配. 这个将反斜线作为转义字符的应用在字符类内部和外部都有效.

比如, 如果你想要匹配”*”字符, 你需要在模式中使用”/*”. 这适用于一个字符在不进行转义会有特殊含义的情况下. 但是, 对于非数字字母的字符, 总是在需要其进行原文匹配的时候在它前面增加一个反斜线, 来声明它代表自己, 这是安全的. 如果要匹配一个反斜线, 那么在模式中使用”//”.

注意: 反斜线在单引号字符串和双引号字符串中都有特殊含义, 因此要匹配一个反斜线, 模式中必须写为”////”. 译注: “////”, 首先它作为字符串, 反斜线会进行转义, 那么转义后的结果是///, 这个才是正则表达式引擎拿到的模式, 而正则表达式引擎也认为/是转义标记, 它会将分隔符/进行转义, 从而得到的是一个错误, 因此, 需要4个反斜线才可以匹配一个反斜线.

如果一个模式被使用PCRE_EXTENDED选项编译, 模式中的空白字符(除了字符类中的)和未转义的#到行末的所有字符都会被忽略. 要在这种情况下使用空白字符或者#, 就需要对其进行转义.

反斜线的第二种用途提供了一种对非打印字符进行可见编码的控制手段. 除了二进制的0会终结一个模式外, 并不会严格的限制非打印字符(自身)的出现, 但是当一个模式以文本编辑器的方式编辑准备的时候, 使用下面的转义序列相比使用二进制字符会更加容易.

/a 响铃字符(十六进制 07)

/cx control-x, x是任意字符

/e 转义(十六进制1B)

/f 换页(十六进制 0C)

/n 换行(十六进制 0A)

/p{xx} 一个符合xx属性的字符, 详细查看unicode属性

/P{xx} 一个不符合xx属性的字符, 详细查看unicode属性

/r 回车(十六进制 0D)

/t 水平制表符(十六进制 09)

/xhh hh十六进制编码的字符, 详细查看unicode属性

/ddd ddd八进制编码的字符, 或者后向引用

“/cx”的确切效果如下: 如果”x”是一个小写字母, 它被转换为大写. 接着, 将字符的第6(十六进制 40, 右数第一个位为第0)取反. 比如”/cz”成为十六进制的1A, “/c{“成为十六进制3B, “/c;”成为十六进制7B.

”/x”后面,读取两个十六进制数(字母可以是大写或小写). UTF-8模式, “/x{…}”允许使用, 花括号内的内容是十六进制有效数字. 它将给出的十六进制数字解释为UTF-8字符代码. 原来的十六进制转义序列, /xhh, 匹配一个双字节的UTF-8字符, 如果它的值大于127

”/0”之后, 读取两个八进制数. 所有情况下, 如果数少于2, 则直接使用. 序列”/0/x/07”指定了两个二进制0紧跟着一个BEL字符. 请确保初始的0之后的两个数字是合法的八进制数.

处理一个反斜线紧跟着的不是0的数字的情况比较复杂. 在字符类外部, PCRE读取它并以十进制读取紧随其后的数字. 如果数值小于10 或者之前捕获到了该数字能够代表的左括号(子组), 整个数字序列被认为是后向引用.后向引用如何工作在后面描述, 接下来就会讨论括号子组.

在一个字符类里面, 或者十进制数大于9并且没有那么多的子组被捕获, PCRE重新读取反斜线后的第三个8进制数字, 并且从最低的8位生成单字节值. 任何的后续数字都代表它们自身. 例如:

/040: 空格的另一种写法

/40 当提供了少于40个子组时, 也是空格

/7 始终是后向引用

/11 可能是后向引用, 也可能是制表符

/011 始终是制表符

/0113 一个制表符紧跟一个3(因为每次最多只读取38进制位)

/113:八进制113代表的字符

/377 8进制37710进制255, 因此代表全1的一个字符

/81: 一个后向引用或者一个二进制0紧跟两个数字81(因为8不是8进制有效数字)

注意, 八进制值的100或者更大的值必须没有前置的0引导, 因为每次最多读取38进制位.

所有序列定义的单字节值都可以在字符类内部或外部使用. 另外, 在字符类中, 序列”/b”解释为退格字符. 字符类外它又有不同的意义(下面有描述)

反斜线的第三种用法是用来描述特定的字符类:

/d 任意十进制数位

/D 任意非十进制数位

/h 任意水平空白字符(PHP 5.2.4)

/H 任意非水平空白字符(PHP 5.2.4)

/s 任意空白字符

/S 任意非空白字符

/v 任意垂直空白字符(PHP 5.2.4)

/V 任意非垂直空白字符(PHP 5.2.4)

/w 任意单词字符

/W 任意非单词字符

上面每一对转义序列都代表了完整字符集中两个不相交的部分, 任意字符一定会匹配其中一个, 同时一定不会匹配另外一个.

单词字符指的是任意字母, 数字, 下划线. 也就是说任意可以组成perl单词的字符. 字母和数字的定义通过PCRE字符表控制, 可以通过指定地域设置使其匹配改变. 比如, 在法国(fr)地域设置中, 一些超过128的字符代码被用于重音字母, 它们可以实用/w匹配.

这些字符类序列在字符类内部或外部都可以出现. 他们每次匹配所代表的字符类型中的一个字符. 如果当前匹配点位于目标字符串末尾, 它们中的所有字符都匹配失败, 因为没有字符让它们匹配了.

反斜线的第四种用法是一些简单的断言. 一个断言指定一个必须在特定位置匹配的条件, 它们不会从目标字符串中消耗任何字符. 接下来我们会讨论使用子组的更加复杂的断言. 反斜线断言包括:

/b 单词边界

/B 非单词边界

/A 目标的开始位置(独立于多行模式)

/Z 目标的结束位置或结束处的新行(独立于多行模式)

/z 目标的结束位置(独立于多行模式)

/G 在目标中的首次匹配位置

这些断言不能出现在字符类中(但是注意, “/b”在字符类中有不同的意义, 表示的是退格(backspace)字符)

一个单词边界表示的是在目标字符串中, 当前字符和前一个字符不同时匹配/w/W(一个比配/w, 一个匹配/W), 或者作为字符串开始或结尾字符的时候当前字符匹配/w.

/A, /Z, /z断言不同于传统的^$(详见下文), 因为他们永远匹配目标字符串的开始和结尾, 而不会受模式修饰符的限制. 它们不受PCRE_MULTILINE, PCRE_DOLLAR_ENDONLY选项的影响. /Z/z之间的不同在于当字符串结束字符时换行符时/Z会将其看做字符串结尾匹配, /z只匹配字符串结尾.

/G断言在指定了$offset参数的preg_match()调用中, 仅在当前匹配位置在匹配开始点的时候才是成功的. $offset的值不为0的时候, 它与/A是不同的. 译注: 另外一点与/A的不同之处在于使用preg_match_all(), 每次匹配/G只是断言是否是匹配结果的开始位置, /A断言的则是匹配结果的开始位置是否在目标字符串开始位置

php4.3.3开始, /Q/E可以用于在模式中忽略正则表达式元字符. 比如: /w+/Q.$./E$会匹配一个或多个单词字符, 紧接着一个点号, 一个$, 一个点号, 最后锚向字符串末尾.

PHP 5.2.4开始. /K可以用于重置匹配. 比如, foot/Kbar匹配”footbar”, 但是得到的匹配结果是”bar”. 但是, /K的使用不会干预到子组内的内容, 比如(foot)/Kbar匹配”footbar”, 第一个子组内的结果仍然会是”foo”. 译注: /K放在子组和子组外面的效果是一样的.

Unicode 字符属性

自从PHP 4.4.05.1.0, 三个额外的转义序列在选用UTF-8模式时用于匹配通用字符类型. 他们是:

/p{xx} 一个有属性xx的字符

/P{xx} 一个没有属性xx的字符

/X 一个扩展的Unicode序列

上面xx代表的属性名用于限制Unicode通常的类别属性. 每个字符都有一个这样的确定的属性, 通过两个缩写的字母指定. 为了与perl兼容, 可以在左花括号{后面增加^表示取反. 比如: /p{^Lu}就等同于/P{Lu}

如果通过/p/P仅指定了一个字母, 它包含所有以这个字母开头的属性. 在这种情况下, 花括号的转义序列是可选的. 比如: /p{L}可以写作/pL

Unicode属性列表

C

Other - 其它

No

Other number - 其它数字

Cc

Control - 控制

P

Punctuation - 标点符号

Cf

Format - 格式

Pc

Connector punctuation - 连接标点符

Cn

Unassigned - 无符号

Pd

Dash punctuation - 横线标点符

Co

Private use - 私有

Pe

Close punctuation - 结束标点符

Cs

Surrogate - 代替

Pf

Final punctuation - 最终标点符

L

Letter -字母

Pi

Initial punctuation - 起始标点符

Ll

Lower case letter - 小写字母

Po

Other punctuation - 其它标点符号

Lm

Modifier letter - 修正符字母

Ps

Open punctuation - 开始标点符

Lo

Other letter - 其它字母

S

Symbol - 符号

Lt

Title case letter - 标题大写字母

Sc

Currency symbol - 货币符号

Lu

Upper case letter - 大写字母

Sk

Modifier symbol - 修正符号

M

Mark - 标记

Sm

Mathematical symbol - 算术符号

Mc

Spacing mark - 空格标记

So

Other symbol - 其它符号

Me

Enclosing mark - 环绕标记

Z

Separator - 分隔符

Mn

Non-spacing mark - 非空格标记

Zl

Line separator - 行分隔符

N

Number - 数字

Zp

Paragraph separator - 段落分隔符

Nd

Decimal number - 十进制数字

Zs

Space separator - 空格分隔符

Nl

Letter number - 字母数字

“Greek”, “InMusicalSymbols”等扩展属性在PCRE中不支持

指定大小写不敏感匹配对这些转义序列不会产生影响, 比如, /p{Lu}始终匹配大写字母.

/X转义匹配任意数量的Unicode字符. /X等价于(?>/PM/pM*)

也就是说, 它匹配一个没有”mark”属性的字符, 紧接着任意多个由”mark”属性的字符. 并将这个序列认为是一个原子组(详见下文). 典型的有”mark”属性的字符是影响到前面的字符的重音符.

Unicode属性来匹配字符并不快, 因为PCRE需要去搜索一个包含超过15000字符的数据结构. 这就是为什么在PCRE中要使用传统的转义序列/d, /w而不使用Unicode属性的原因.

^$

在一个字符类外面, 在默认匹配模式下, ^是一个断言当前匹配点位于目标字符串开始处的断言. 在一个字符类内部, ^表明这个字符类中描述的字符取反(详见下文).

^并不一定要是模式的第一个字符, 但是如果处于某个可选分支时, 它应该是该分支的首字符. 如果所有选择分支都以^开头, 这就是说, 如果模式限制为只匹配目标的开头, 它被称为是一个紧固模式.(同样也有其他方式可以构造出紧固模式)

$是用于断言当前匹配点位于目标字符串末尾, 或当目标字符串以换行符结尾时当前匹配点位于该换行符位置(默认情况). $不一定要作为模式的最后一个字符, 但是如果它在某个可选分支中时, 就应该位于该分支的末尾. $在字符类中没有特殊的意义.

$的意义可以通过在编译或匹配时设置PCRE_DOLLAR_ENDONLY改变为只匹配字符串末尾. 这不会影响/Z断言的行为.

^$字符的意义在PCRE_MULTILINE选项被设置时会发生变化. 当在这种情况下时, 它们匹配每一个换行符后面的和前面的字符, 另外, 也会匹配目标字符串的开始和结束. 比如, 模式/^abc$/在多行模式下会成功匹配目标字符串”def/nabc”, 而正常情况下不会.因此, 由于所有的可选分支都以^开始, 在单行模式下这成为紧固模式, 然而在多行模式下, 这是非紧固的. PCRE_DOLLAR_ENDONLY选项在PCRE_MULTILINE设置后失效.

注意: /A, /Z, /z等转义序列可以在任何模式下用于匹配目标字符串的开始和结束位置. 并且如果模式的所有分支都以/A开始, 它同样是紧固的, 而与PCRE_MULTILINE是否设置.

句点

在字符类外部, 模式中的句点匹配目标字符串中的任意字符, 包括非打印字符, 但是(默认)不包括换行符. 如果PCRE_DOTALL被设置, 句点就会匹配换行符. 句点的处理和^, $的处理没有关联, 它们唯一的关系是它们都涉及到了换行符. 句点在字符类中没有任何意义.

/C可以被用于匹配单字节, 也就是说在UTF-8模式下, 句点可以匹配多字节字符.

方括号

左方括号开始一个字符类的描述, 并以方中括号结束. 单独的一个右方括号没有特殊含义. 如果一个右方括号需要作为一个字符类中的成员, 那么可以将它写在字符类的首字符处(如果使用了^取反, 那么是第二个)或者使用转义符.

一个字符类在目标字符串中匹配一个单独的字符; 该字符必须是字符类中定义的字符集合的其中一个, 除非使用了^对字符类取反. 如果^需要作为一个字符类的成员, 确保它不是该字符类的首字符, 或者对其进行转义即可.

例如, 字符类[aeiou]匹配所有的小写元音字母, [^aeiou]匹配所有非元音字母的字符. 注意: ^只是一个通过枚举指定那些不存在字符类之中的字符的便利符号. 而不是断言, 它仍然会从目标字符串中消耗一个字符, 并且如果当前匹配点在目标字符串末尾, 匹配将会失败.

换行符在字符类中没有任何特殊涵义, PCRE_DOTALLPCRE_MULTILINE选项都无关. 一个字符类比如[^a]始终会匹配换行符

在字符类中, 一个中划线(减号 -)可以用于指定从一个字符到另一个字符的范围. 比如, [d-m]匹配dm之间的所有字符, 这个集合时闭合的. 如果中划线自身要在一个字符类中描述, 它必须被转移或者出现在一个不会被解释为一个范围的位置, 典型的比如字符类开始或结束位置.

在一个字符范围描述后面不能使用右中括号. 比如一个模式[W-]46]被解释为一个包含W-的字符类, 后面紧跟字符串”46]”, 因此它可以匹配”W46]””-46]”. 然而, 如果中括号是经过转义的, 它将会被解释为范围的终点, 因此[W-/]46]就会被解释为一个单独的包含W]范围内所有字符以及4,6的字符类. 8进制或16进制描述的中括号同样可以用于作为范围的终点.

范围操作以ASCII整理排序. 它们可以用于为字符指定数值, 比如[/000-/037]. 如果在大小写不敏感匹配模式下使用一个包含字母的范围, 则同时匹配它的大小写形式. 比如[W-c]在不区分大小写匹配时等价于[][/^_`wxyzabc], 并且, 如果使用了”fr”(法国)的地域设置字符表时, [/xc8-xcb]将会在所有模式下匹配重音E字符.

字符类/d, /D, /s, /S, /w/W也可以出现在一个字符类中, 用以将其匹配的字符类加入到新的自定义字符类中. 比如, [/dABCDEF]匹配任意合法的16进制数. ^可以很方便的制定严格的字符类, 比如[^/W_]匹配任何字母或数字, 但不匹配下划线

perl支持POSIX字符类符号. 这种字符类使用[::]闭合. PCRE同样支持这些字符类, 比如, [01[:alpha:]%]匹配”0”, “1”, 任意字母或”%”. 支持的字符类如下:

[:alnum:] 字母和数字

[:alpha:] 字母

[:ascii:] 0-127ascii字符

[:blank:] 空格和制表符

[:cntrl:] 控制字符

[:digit:] 十进制数(/d)

[:graph:] 打印字符, 不包括空格

[:lower:] 小写字母

[:print:] 打印字符, 包括空格

[:punct:] 打印字符, 不包括字母和数字

[:space:] 空白字符(/s多个垂直指标)

[:upper:] 大写字母

[:word:] 单词字符(/w)

[:xdigit:] 十六进制字符

空白字符有HT(9), LF(10), VT(11), FF(12), CR(13), space(32). 注意, 这个列表包含了垂直制表符. 这使得space不同于/s, 因为它不包含垂直制表符(为了向perl兼容)

[:word:]是一个perl扩展, [:blank:]是一个从Perl5.8中来的GNU扩展. 另外一个perl扩展是取反, 通过前置一个^. 比如, [12[:^digit:]]匹配”1”, “2”或任何非数字字符

UTF-8模式, 大于128的字符值不会匹配任何POSIX字符类.

竖线 |

竖线字符用于分离模式中的可选路径. 比如模式gilbert|Sullivan匹配”gilbert”或者”sullivan”. 竖线可以在模式中出现任意多个, 并且允许有空的可选路径(匹配空字符串). 匹配的处理从左到右尝试每一个可选路径, 并且使用第一个成功匹配的. 如果可选路径在子组(下面定义), 成功匹配表示同时匹配了子模式中的分支以及主模式中的其他部分.

内部选项设置

PCRE_CASELESS, PCRE_MULTILINE, PCRE_DOTALL, PCRE_UNGREEDY, PCRE_EXTRA, PCRE_EXTENDED以及PCRE_DUPNAMES等模式修饰设置可以在模式内部通过一个perl选项字符序列来设置, 语法为: (?修饰符), 可选的修饰符有:

i PCRE_CASELESS

m PCRE_MULTILINE

s PCRE_DOTALL

x PCRE_EXTENDED

U PCRE_UNGREEDY

X PCRE_EXTRA

J PCRE_INFO_JCHANGED

比如, (?im)设置表明多行大小写不敏感匹配.同样可以用它来取消这些设置, 比如(?im-sx)设置了PCRE_CASELESS, PCRE_MULTILINE, 但是同时取消了PCRE_DOTALLPCRE_EXTENDED. 如果一个字母即出现在-之前, 也出现在-之后, 这个选项被取消设置.

当一个选项在模式的最上级(也就是说不在子组中), 这个改变会影响模式中剩余部分. 比如/ab(?i)c/仅仅匹配”abc””abC”. 这个形式在PCRE 4.0(PHP 4.3.3)中被改变. 在此之前的版本中, /ab(?i)c/行为和/abc/i完全一致

如果一个选项在子组中设置, 产生的影响是不同的.这是perl 5.005中行为的一个变种. 一个选项在子组内部设置, 仅仅改变子组中剩余的部分, 因此(a(?i)b)c仅仅匹配”abc””aBc”(假设没有使用PCRE_CASELESS选项). 这就意味着选项在模式的不同位置可以造成不同的影响. 在同一个子模式中, 一个分支的选项设置回穿透到后面剩余的其他分支中去. 比如(a(?i)b|c)匹配”ab”, “aB”, “c””C”, 尽管在匹配”C”时第一个分支会在选项被设定前就被丢弃. 这是因为选项的设定是在编译器确定的, 否则可能会带来非常怪异的行为.

PCRE专用选项PCRE_UNGREEDYPCRE_EXTRA可以和perl兼容选项以同样的方式来改变, 分别使用字母UX. (?X)标记设定有些特殊, 它必须出现在任何其他特性之前, 最好放在最开头的位置.

子组(子模式)

子组通过圆括号分隔界定, 并且它们可以嵌套. 将一个模式中的一部分标记为子组(子模式)主要是来做两件事情:

<!--[if !supportLists]-->1. <!--[endif]-->将可选分支局部化. 比如, 模式cat(arcat|erpillar|)匹配”cat”, “cataract”, “caterpillar”中的一个, 如果没有圆括号的话, 它匹配的则是”cataract”, “erpillar”以及空字符串.

<!--[if !supportLists]-->2. <!--[endif]-->将子组设定为捕获子组(向上面定义的). 当整个模式匹配后, 目标字符串中匹配子组的部分将会通过pcre_exec()ovector参数回传给调用者. 左括号从左至右出现的次序就是对应子组的下标(1开始), 可以通过这些下标数字来获取捕获子模式匹配结果.

比如, 如果字符串”the red king”使用模式((red|white) (king|queen))进行匹配, 模式匹配到的结果是array(“red king”, ”red king”, “red”, “king”)的形式, 其中第0个元素是整个模式匹配的结果, 后面的三个元素依次为三个子组匹配的结果. 它们的下表分别为1 2 3.

事实上, 圆括号履行的两种功能并不总是有用的. 经常我们会有一种需求需要使用子组进行分组, 但又不需要(单独的)捕获它们. 在子组定义的左括号后面紧跟字符串”?:”会使得该子组不被单独捕获, 并且不会对其后子组序号的计算产生影响. 比如, 如果字符串”the white queen”匹配模式((?:red|white) (king|queen)), 匹配到的结果会是array(“white queen”, “white queen”, “white queen”), 只捕获了最外面的和king|queen这两个子组. 捕获子组序号的最大值是99, 最大允许拥有的所有子组(包括捕获的和非捕获的)的最大数量为200.

为了方便简写, 如果需要在非捕获子组开始位置设置选项, 选项字母可以位于?:之间, 比如:

(?i:Saturday|Sunday)(?:(?i)Saturday|Sunday)两种写法都合法.

上面两种写法实际上是相同的模式. 因为可选分支会从左到右尝试每个分支, 并且选项没有在子模式结束前被重置, 并且由于选项的设置会穿透对后面的其他分支产生影响, 因此, 上面的模式都会匹配”SUNDAY”以及”Saturday”

php 4.3.3, 可以对子组使用(?P<name>pattern)的语法进行命名. 这个子模式将会在匹配结果中同时以其名称和顺序(数字下标)出现. php 5.2.2中又增加了两种味子组命名的语法: (?<name>pattern)(?’name’pattern)

重复/量词

重复次数是通过量词指定的, 可以紧跟在下面元素之后:

<!--[if !supportLists]-->l <!--[endif]-->单独的字符, 可以是经过转义的

<!--[if !supportLists]-->l <!--[endif]-->元字符.(句点)

<!--[if !supportLists]-->l <!--[endif]-->字符类

<!--[if !supportLists]-->l <!--[endif]-->后向引用(下一部分介绍)

<!--[if !supportLists]-->l <!--[endif]-->子组(除非它是一个断言, 见下)

一般的重复量词指定了一个最小数值和一个最大数值的匹配次数, 通过花括号包裹两个数字, 两个数字之间用逗号隔开的语法定义. 两个数值都必须小于65536, 并且第一个数字必须小于等于第二个. 比如: z{2,4}匹配”zz”, “zzz”, “zzzz”. 单个的右花括号不是特殊字符. 如果第二个数字被省略, 但是逗号仍然存在, 就代表没有上限; 如果第二个数字和逗号都被省略, 那么这个量词就限定的是一个确定次数的匹配. 比如[aeiou]{3,}匹配至少三个连续的元音字母, 但是同时也可以匹配更多, /d{8}则只能匹配8个数字. 左花括号出现在不允许使用量词的位置或者与量词语法不匹配时, 被认为是一个普通字符, 对它自身进行原文匹配. 比如, {,6}就不是一个量词, 会按照原文匹配四个字符”{,6}”

量词{0}是被授权的, 它会导致的行为是认为前面的项和量词不存在.

为了方便(以及历史的兼容性), 最常用的三个量词都有单字符缩写

* 等价于{0,}

+ 等价于{1,}

? 等价于{0,1}

可以通过一个不匹配任何字符的子模式后面紧跟一个匹配0或多个字符的量词来构造一个没有上限的无限循环. 比如: (a?)*

早期版本的perlpcre对于这种模式会在编译期得到一个错误. 然而, 由于这在某些情况下是有用的, 因此现在也接受这种模式了, 但是如果任何子模式的重复确实匹配不到任何字符, 循环会被强制跳出.

默认情况下, 量词都是贪婪, 也就是说, 它们会在不导致模式匹配失败的前提下,尽可能多的匹配字符(直到最大允许的匹配次数). 这种问题的典型示例就是尝试匹配C语言的注释. 出现在/**/之间的所有内容都被认为是注释, 在注释中间, 可以允许出现单独的*/. C注释匹配的一个尝试是使用模式//*.*/*/, 假设将此模式应用在字符串”/* first comment*/ not comment /*second comment*/”它会匹配到错误的结果, 也就是整个字符串, 这是因为量词的贪婪性导致的, 它会尝试尽可能多的匹配字符.

然而, 如果一个量词紧跟着一个?(问号)标记, 它就会成为懒惰(非贪婪)模式, 它不再尽可能多的匹配, 而是尽可能少的匹配. 因此模式//*.*?/*/C的注释匹配上将会正确的执行. 各个量词自身的意义并不会改变, 而是由于加入了?使其首选的匹配次数发生改变. 不要将?的这个用法和它作为量词的用法混淆. 因为它又两种用法, 因此有时它会出现量词, 比如/d??/d会更倾向于匹配一个数字, 但同时如果为了达到整个模式匹配的目的, 它也可以接受两个数字的匹配. 译注: 以模式/w/d??/d/w为例, 对于字符串”a33a”, 虽然/d??是非贪婪的, 但由于如果使用贪婪会导致整个模式不匹配, 所以, 最终它选择的仍然是匹配到一个数字.

如果PCRE_UNGREEDY选项被设置(一个在perl中不可用的选项), 那么量词默认情况下就是非贪婪的了. 但是, 单个的量词可以通过紧跟一个?来使其成为贪婪的. 换句话说, PCRE_UNGREEDY这个选项逆转了贪婪的默认行为.

量词后面紧跟一个”+”占有. 它会吃掉尽可能多的字符, 并且不关注后面的其他模式, 比如.*abc匹配”aabc”, 但是.*+abc不会匹配, 因为.*+会吃掉整个字符串, 从而导致后面剩余的模式得不到匹配. php 4.3.3, 可以实用占有符(+)修饰量词来达到提升速度的目的.

当一个子组受最小数量大于1或有一个最大数量限制的量词修饰时, 按照最小或最大的数量的比例需要更多的存储用于编译模式.

如果一个模式以.*.{0,}开始并且PCRE_DOTALL选项开启(等价于perl/s), 也就是允许.匹配换行符, 那么模式会隐式的紧固, 因为不管怎么样, 接下来都会对目标字符串中的每个字符位置进行尝试, 因此在第一次之后, 在任何位置都不会有一个对所有匹配重试的点. PCRE 会想对待/A一样处理这个模式. 在我们已知目标字符串没有包含换行符的情况下, 当模式以.*开始的时候我们为了获得这个优化, 值得设置PCRE_DOTALL, 或者选择使用^明确指明锚定.

译注: 这里的优化指模式不匹配之后, 不会回头再来查找下一个位置, 比如没有设置PCRE_DOTALL, 并且目标字符串第一个字符时换行符, 那么模式尝试第一个字符, 发现不匹配, 会重新用模式从第二个字符位置开始进行尝试. 而使用了PCRE_DOTALL, 是肯定匹配的….同理, 当使用了^或者/A的限定是, 模式一旦不匹配, 都可以直接退出, 而不用在目标字符串下一个位置再一次开始整个模式的匹配.

当一个捕获子组时重复的时, 捕获到的该子组的结果是最后一次迭代捕获的值. 比如, (tweedle[dume]{3}/s*)+匹配字符串”tweedledum tweedledee”, 得到的的子组捕获结果是”tweedledee”. 然而, 如果是嵌套的捕获子组, 相应的捕获值可能会被设置到之前的迭代中. 比如, /(a|(b))+/匹配字符串”aba”, 第二个捕获子组得到的结果会是”b”. 译注: 不理解然而之后的部分, 以例子说明, b是第二个子组最后一次捕获到的结果, 所以, 第二个子组最后结果是b, 这是符合然而之前描述的规则的.

后向引用

在一个字符类外面, 反斜线紧跟一个大于0(可能还有一位数)的数字就是一个到模式中之前出现的某个捕获组的后向引用.

如果紧跟反斜线的数字小于10 它总是一个后向引用, 并且如果在模式中没有这么多的捕获组会引发一个错误. 换一种说法, 被引用的括号不能少于被引用的小于10的数量. 查看上面的反斜线部分查看具体的数字处理方式.

一个后向引用会直接匹配被引用捕获组在目标字符串中实际捕获到的内容, 而不是匹配子组模式的内容. 因此, 模式(sens|respons)e and /1ibility将会匹配”sense and sensibility””response and responsibility”, 而不会匹配”sense and responsibility”. 如果在后向引用时被强制进行了大小写敏感匹配, 比如((?i)rah)/s+/1匹配”rah rah””RAH RAH”, 但是不会匹配”RAH rah”, 即使原始捕获子组自身是不区分大小写的. 译注: 这里其实要考虑的是后向引用期望得到的内容是和那个被引用的捕获子组得到的内容是完全一致的(当然, 我们可以通过在后向引用之前设定内部选项使其不区分大小写, 或者增加模式修饰符, 同样可以达到不区分大小写的目的, 但是, 这种做法实际上是从外部对其行为进行了控制.)

可能会有超过一个的后向引用引用相同的子组. 一个子组可能并不会真正的用于特定的匹配, 此时, 任何对这个子组的后向引用也都会失败. 比如, 模式(a|(bc))/2总是在匹配”a”开头而不是”bc”开头的字符串时失败. 因为可能会有多达99个后向引用, 所有紧跟反斜线后的数字都可能是一个潜在的后向引用计数. 如果模式在后向引用之后紧接着还是一个数值字符, 那么必须使用一些分隔符用于终结后向引用语法. 如果PCRE_EXTENDED选项被设置了, 可以使用空格来做. 其他情况下可以使用一个空的注释.

如果一个后向引用出现在它所引用的子组内部, 它的匹配就会失败. 比如, (a/1)就不会得到任何匹配. 然而这种引用可以用于内部的子模式重复. 比如, 模式(a|b/1)+会匹配任意数量的”a”组成的字符串以及”aba”, “ababba”等等(译注: 因为子组内部有一个可选路径, 可选路径中有一条路能够完成匹配, 在匹配完成后, 后向引用就能够引用到内容了).在每次子模式的迭代过程中, 后向引用匹配上一次迭代时这个子组匹配到的字符串. 为了做这种工作, 模式必须满足这样一个条件, 模式在第一次迭代的时候, 必须能够保证不需要匹配后向引用. 这种条件可以像上面的例子用可选路径来实现,也可以通过使用最小值为0的量词修饰后向引用的方式来完成.

php 5.2.2之后, /g转义序列可以用于子模式的绝对和相对引用. 这个转义序列必须紧跟一个无符号数字或一个负数, 可以选择性的使用括号对数字进行包裹. 序列/1 /g1,/g{1}之间是同义词关系. 这种用法可以消除使用反斜线紧跟数值描述反向引用时候产生的歧义. 这种转义序列有利于区分后向引用和八进制数字字符, 也使得后向引用后面紧跟一个原文匹配数字变的更明了, 比如 /g{2}1

/g转义序列紧跟一个负数代表一个相对的后向引用. 比如: (foo)(bar)/g{-1}可以匹配字符串”foobarbar”, (foo)(bar)/g{2}可以匹配”foobarfoo”. 这在长的模式中作为一个可选方案, 用来保持对之前一个特定子组的引用的子组序号的追踪.

后向引用也支持使用子组名称的语法方式描述, 比如(?P=name)或者php5.2.2开始可以实用/k<name>/k’name’. 另外在php5.2.4中加入了对/k{name}/g{name}的支持

断言

一个断言就是一个对当前匹配位置之前或之后的字符的测试, 它不会实际消耗任何字符. 简单的断言代码有/b, /B, /A, /Z, /z, ^, $等等. 更加复杂的断言以子组的方式编码. 它有两种类型: 前瞻断言(从当前位置向前测试)和后瞻断言(从当前位置向后测试)

一个断言子组的匹配还是通过普通方式进行的, 不同在于它不会导致当前的匹配点发生改变. 前瞻断言中的正面断言(断言此匹配为真)”(?=”开始, 消极断言”(?!”开头. 比如, /w+(?=;)匹配一个单词紧跟着一个分号但是匹配结果不会包含分号, foo(?!bar)匹配所有后面没有紧跟”bar””foo”字符串. 注意一个类似的模式(?!foo)bar, 它不能用于查找之前出现所有不是”foo””bar”匹配, 它会查找到任意的”bar”出现的情况, 因为(?!foo)这个断言在接下来三个字符时”bar”的时候是永远都TRUE. 前瞻断言需要达到的就是这样的效果.

后瞻断言中的正面断言以”(?<=”开始, 消极断言以”(?<!”开始. 比如, (?<!foo)bar用于查找任何前面不是”foo””bar”. 后瞻断言的内容被严格限制为只能用于匹配定长字符串. 但是, 如果有多个可选分支, 它们不需要拥有相同的长度. 比如(?<=bullock|donkey)是允许的, 但是(?<!dogs?|cats?)将会引发一个编译期的错误.在最上级分支可以匹配不同长度的字符串是允许的. 相比较于perl 5.005而言, 它会要求多个分支使用相同长度的字符串匹配. (?<=ab(c|de))这样的断言是不允许的, 因为它单个的顶级分支可以匹配两个不同的长度, 但是它可以接受使用两个顶级分支的写法(?<=abc|abde)这样的断言实现, 对于每个可选分支, 暂时将当前位置移动到尝试匹配的当前位置之前的固定宽度处. 如果在当前没有足够的字符就视为匹配失败.后瞻断言与一次性子组结合使用可以用来匹配字符串结尾; 一个例子就是在一次性子组上给出字符串结尾.

多个断言(任意顺序)可以同时出现. 比如(?<=/d{3})(?<!999)foo匹配前面有三个数字但不是”999”的字符串”foo”. 注意, 每个断言独立应用到对目标字符串该点的匹配. 首先它会检查前面的三位都是数字, 然后检查这三位不是”999”. 这个模式不能匹配”foo”前面有三位数字然后紧跟3位非9996个字符的字符串, 比如, 它不匹配”123abcfoo”. 匹配”123abcfoo”这个字符串的模式可以是(?<=/d{3}…)(?<!999)foo.

这种情况下, 第一个断言查看(当前匹配点)前面的6个字符, 检查前三个是数字, 然后第二个断言检查(当前匹配点)前三个字符不是”999”

断言可以以任意复杂度嵌套. 比如(?<=(?<!foo)bar)baz匹配前面有”bar”但是”bar”前面没有”foo””baz”. 另外一个模式(?<=/d{3}…(?<!999))foo则匹配前面有三个数字字符紧跟3个不是999的任意字符的”foo”.

断言子组时非捕获子组, 并且不能用量词修饰, 因为对同一件事做多次断言是没有意义的.如果所有的断言都包含一个捕获子组, 那么为了在整个模式中捕获子组计数的目的, 它们都会被计算在内. 然而, 子字符串的捕获仅可以用于正面断言, 因为对于消极的断言是没有意义的.

将断言计算在内, 可以拥有的最大子组数量是200.

一次性子组

对于同时有最大值和最小值量词限制的重复项, 在匹配失败后, 紧接着会以另外一个重复次数重新评估是否能使模式匹配. 当模式的作者明确知道执行上没有问题时, 通过改变匹配的行为或者使其更早的匹配失败以阻止这种行为是很有用的.

考虑一个例子, 模式/d+foo应用到目标行123456bar:

在匹配了6个数字后匹配”foo”时失败, 通常的行为时匹配器尝试使/d+只匹配5个数字, 只匹配4个数字, 在最终失败之前依次进行尝试. 一次性子组提供了一种特殊的意义, 当模式的一部分得到匹配后, 不再对其进行重新评估, 因此匹配器在第一次匹配”foo”失败后就能立刻失败. 语法符号是另外一种特殊的括号, (?>开始, 比如(?>/d+)bar

这种括号对模式的一部分提供了锁定”, 当它包含一个匹配之后, 会阻止未来模式失败后对它内部的后向回溯. 后向回溯在这里失效, 其他工作照常进行.

换一种说法, 如果在目标字符串中当前匹配点是锚点, 这种类型的子组匹配的字符串等同于一个独立的模式匹配.

一次性子组不是捕获子组. 如上面的例子, 简单而言, 就是尽其所能吃掉尽可能多的匹配字符. 因此, 尽管/d+/d+?都会调整要匹配的数字的个数以便模式的其他部分匹配, (?>/d+)却仅能匹配整个数字序列.

这个(语法)结构可以包含任意复杂度的字符, 也可以嵌套.

一次性子组可以和后瞻断言结合使用来指定在目标字符串末尾的有效匹配. 考虑当一个简单的模式比如abcd$应用到一个不匹配的长字符串上. 由于匹配时从左到右处理的, PCRE会从目标中查找每一个”a”然后查看是否紧接着会匹配模式的剩余部分. 如果模式是^.*abcd$, 那么初始的.*将首先匹配整个字符串,但是当它失败后(因为紧接着不是”a”), 它会回溯所有的匹配,依次吐出最后1个字符, 倒数第2个字符等等. 从右向左查找整个字符串中的”a”, 因此, 我们不能很好的退出. 然而, 如果模式写作^(?>.*)(?<=abcd)那么它就不会回溯.*这一部分, 它仅仅用于匹配整个字符串. 后瞻断言对字符串末尾的后四个字符做了一个测试. 如果它失败, 匹配立即失败. 对于长字符串, 这个模式将会带来显著的处理时间上的性能提升.

当一个模式中包含一个子组自己可以无限重复并且内部有无限重复元素时, 使用一次性子组是避免一些失败匹配消耗大量时间的唯一途径. 模式(/D+|</d+>)*[!?]匹配一个不限制数目的非数字字符或由<>闭合的数字字符紧跟着!?. 当它匹配的时候, 运行时快速的. 然而, 如果它应用到aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa上将会在报告错误之前消耗很多时间. 这是因为字符串可以用于两种重复规则, 并且需要为两种重复规则都分配进行尝试. (示例的结尾使用[!?]而不是单个的字符, 是因为PCREperl都会对模式最后是一个单独字符时的快速报错有优化. 它们会记录最后需要匹配的单个字符, 当它们没有出现在字符串中时快速报错.) 如果模式修改为((?>/D+)|</d+>)*[!?]就会快速得到报错.(译注: 对于这里给出的模式, 当目标字符串更长的时候, 消耗时间会迅速增加, 慎用.)

条件子组

可以使匹配器根据一个断言的结果, 或者之前的一个捕获子组是否匹配来条件式的匹配一个子组或者在两个可选子组中选择. 条件子组的两种语法如下:

(?(condition)yes-pattern)

(?(condition)yes-pattern|no-pattern)

如果条件满足, 使用yes-pattern, 其他情况使用no-pattern(如果指定了). 如果有超过2个的可选子组, 会产生给一个编译期错误.

条件一共有两种. 如果在(condition)的括号内是数字组成的文本, 条件在该数字代表的(之前的)子组得到匹配时满足(即使用yes-pattern). 考虑下面的模式, 为了使其易于阅读其中增加了一些空白字符(查看PCRE_EXTENDED选项)并且将其分为三个部分: ( /( )? [^()]+ (?(1) /) )

模式的第一部分匹配一个可选的左括号, 并且如果该字符出现, 设置其为第一个子组的捕获子串. 第二部分匹配一个或多个非括号字符. 第三部分是一个条件子组, 它会测试第一个子组是否匹配, 如果匹配到了, 也就是说目标字符串以左括号开始, 条件为true, 那么使用yes-pattern也就是这里需要匹配一个右括号. 其他情况下, 既然no-pattern没有出现, 这个子组就不匹配任何东西. 换句话说, 这个模式匹配一个没有括号的或者闭合括号包裹的字符序列.

如果条件式字符串(R), 它在得到对模式或子模式的递归调用时满足. 最上级”, 条件总是false.

如果条件不是数字序列或(R), 它就必须是一个断言. 这里的断言可以使任意的, 积极, 消极, 正向, 后向都是可以的. 考虑这个模式, 同样为了方便阅读, 增加了一些空白字符, 并且在第二行有两个可选路径

(?(?=[^a-z]*[a-z])

/d{2}-[a-z]{3}-/d{2} | /d{2}-/d{2}-/d{2} )

条件式 一个正向积极断言, 匹配一个可选的非小写字母字符序列紧接着一个小写字母. 换一种说法, 它测试目标中至少出现一个小写字母, 如果小写字母发现, 目标匹配第一个可选分支, 其他情况下它匹配第二个分支. 这个模式匹配两种格式的字符串: dd-aaa-dddd-dd-dd. aaa代表小写字母, dd是数字.

注释

字符序列(?#标记开始一个注释直到遇到一个右括号. 不允许嵌套括号. 注释中的字符不会作为模式的一部分参与匹配.

如果设置了PCRE_EXTENDED选项, 一个字符类外部的未转义的#字符就代表本行剩余部分为注释.

递归模式

考虑匹配圆括号内字符串的问题, 允许无限嵌套括号. 如果不使用递归, 最好的方式是使用一个模式匹配固定深度的嵌套.它不能处理任意深度的嵌套. perl 5.6提供了一个实验性的功能允许正则表达式递归. 特殊项(?R)提供了递归的这种特殊用法. 这个PCRE模式解决了圆括号问题(假设PCRE_EXTENDED选项被设置了, 因此空白字符被忽略): /( ( (?>[^()]+) | (?R) )* /)

首先, 它匹配一个左括号. 然后它匹配任意数量的非括号字符序列或一个模式自身的递归匹配(比如, 一个正确的括号子串), 最终, 匹配一个右括号.

这个例子模式包含无限重复的嵌套, 因此使用了一次性子组匹配非括号字符, 这在模式应用到模式不匹配的字符串时非常重要. 比如, 当它应用到(aaaaaaaaaaaaaaaaaaaaaaaaaaaaa()时就会很快的产生不匹配结果. 然而, 如果不使用一次性子组, 这个匹配将会运行很长时间, 因为有很多途径让+*重复限定分隔目标字符串, 并且在报告失败之前需要测试所有路径.

所有捕获子组最终被设置的捕获值都是从递归最外层子模式捕获的值. 如果上面的模式匹配(ab(cd)ef), 捕获子组最终被设置的值为”ef”, 即顶级得到的最后一个值. 如果增加了额外的括号, /( ( ( (?>[^()]+) | (?R) )* ) /), 捕获到的字符串就是顶层括号的匹配内容”ab(cd)ef”. 如果在模式中有超过15个捕获括号, PCRE在递归期间就会使用pcre_malloc分配额外的内存来存储数据, 随后通过pcre_free释放他们. 如果没有内存可被分配, 它就仅保存前15个捕获括号, 在递归内部无法给出内存不够用的错误.

php4.3.3开始, (?1), (?2)等可以用于递归子组. 这同样可以用于命名子组: (?P>name)(?P&name)

如果递归子组语法在它提到的子组括号外部使用(无论是子组数字序号还是子组名称), 这个操作就相当于程序设计语言中的子程序. 前面一些有一个例子指出模式(sens|respons)e and /1ibility匹配”sense and responsibility””response and responsibility”, 但是不匹配”sense and responsibility”. 如果用模式(sens|respons)e and (?1)ibility替代, 它会像匹配那两个字符串一样匹配”sense and responsibility”. 这种引用方式意义是紧接着匹配引用的子模式.(译注: 后向引用只匹配引用的子组之前匹配的结果, 这里的递归语法引用是拿引用的子模式重新匹配)

目标字符串的最大长度是int型变量可以存储的最大正整数. 然而, PCRE使用递归处理子组和无限重复. 这就是说对于某些模式可用的栈空间可能会受目标字符串限制.

性能

模式中一些项可能比其他一些更加高效. 比如使用[aeiou]这样的字符类会比可选路径(a|e|i|o|u)高效. 一般而言, 用尽可能简单的构造描述需求是最搞笑的. Jeffrey Friedl(精通正则表达式)中包含了很多关于正则表达式性能的讨论.

当一个模式以.*开始并且设置了PCRE_DOTALL选项时, 模式通过PCRE隐式锚定, 因为它可以匹配字符串的开始. 然而, 如果PCRE_DOTALL没有设置, PCRE不能做这个优化, 因为.元字符不能匹配换行符, 如果目标字符串包含换行符, 模式可能会从一个换行符后面开始匹配, 而不是最开始位置. 比如, 模式(.*) second匹配目标字符串”first/nand second”(/n是一个换行符)第一个捕获子组结果是”and”. 为了这样做, PCRE尝试从目标字符串中每个换行符后开始匹配.

如果你使用模式匹配没有换行符的目标字符串, 可以通过设置PCRE_DOTALL或以^.*开始的模式明确指示锚定以获取最佳性能. 这样节省了PCRE沿目标字符串扫描查找换行符重新开始的时间.

小心模式中的无限重复嵌套. 这在应用到不匹配字符串时可能会导致运行时间很长. 考虑模式片段(a+)*

这个模式可以有33种方式匹配”aaaa”, 并且这个数字会随着字符串的长度的增加迅速增加. (*重复可以匹配0,1,2,3,4, 并且除了0外每种情况+都有不同次数的匹配对应). 当模式的剩余部分导致整个匹配失败的时候, PCRE原则上回尝试每种可能的变化, 这将会非常耗时.

对于一些简单的情况的优化是像(a+)*b这样紧接着使用原文字符串.. 在着手正式匹配工作之前, PCRE检查目标字符串后面是否有”b”字符, 如果没有就立即失败. 然而当紧接着没有原文字符的时候这个优化是不可用的. 你可以比较观察(a+)*/d和上面模式的行为差异. 前者在应用到整行的”a”组成的字符串时几乎是立即报告失败, 而后者在目标字符串长于20个字符时, 时间消耗就相当可观.

分享到:
评论

相关推荐

Global site tag (gtag.js) - Google Analytics