首页 » 精通正则表达式(第3版) » 精通正则表达式(第3版)全文在线阅读

《精通正则表达式(第3版)》Preg函数罗列

关灯直达底部

The Preg Functions

本节从最基本的“这个正则表达式能否在字符串中找到匹配”开始,详细介绍各个函数。

preg_match

使用方法

preg_match(pattern,subject[,matches[,flags [,offset]]])

参数简介

pattern 分隔符包围起来的正则表达式,可能出现修饰符(☞444)。

subject 需要搜索的目标字符串。

matches 非强制出现,用来接受匹配数据。

flags 非强制出现,此标志位会影响整个函数的行为。这里只容许出现一个标志位,

PREG_OFFSET_CAPTURE(☞452).

offset非强制出现,从0开始,表示匹配尝试开始的位置(☞453)。

返回值

如果找到匹配,就返回true,否则返回false。

讲解

最简单的用法是:

preg_match($pattern,$subject)

如果$pattern在$subject中能找到匹配,就会返回true。下面有几个简单的例子:

捕获匹配数据

preg_match的第3个参数如果出现,则会用来保存匹配结果的信息。用户可以照自己的意愿使用任何变量,不过最常用的名字是$matches。在本书中,如果我在特定的例子之外提到$matches,指的就是“preg_match接收的第3个参数”。

匹配成功之后,preg_match返回true,并按如下规则设置$matches:

如果使用了命名分组,$matches中也会保存对应的元素(下一节有这样的例子)。

第5章中(☞191)曾出现过这个简单的例子:

最好是在preg_match返回true的情况下用$matches(随便你怎么命名)。如果匹配不成功,会返回 false,或者错误(例如模式错误或函数标志位设置错误)。有的错误发生之后,$matches 是空数组,但也有时候它的值不会变化,所以我们不能认为,$matches 不为空就表示匹配功。

下面这个例子使用了3组捕获型括号:

数组结尾“未参与匹配”的元素会被忽略

如果一组捕获型括号没有参与最终匹配,它会在对应的$matches中生成一个空字符串(注2)。需要说明的是,$matches末尾的空字符串都会被忽略。在前面那段程序中,如果「(/d+)」参与了匹配,$matches[3]会保存一个数值,否则,$matches[3]根本就不会存在。

命名捕获

如果我们用命名捕获(☞138)重写之前的例子,正则表达式会长一些,不过代码更容易阅读:

命名捕获看起来更清晰,这样我们不需要把$matches的内容复制给各个变量,就能直接使用变量名,而不是$matches,例如这样:

如果使用了命名捕获,按数字编号的捕获仍然会插入$matches。例如,在匹配$url(值为‘http://regex.info’)之后,之前例子中的$UrlInfo包含:

这样的重复有点浪费,但这是获得命名捕获的便捷和清晰所必须付出的代价。为清晰起见,我不推荐同时使用命名和数字编号来访问$matches的元素,当然用$matches[0]表示全局匹配例外。

请注意,数组中不包括编号为3和名称为‘port’的入口(entry),因为这一组捕获型括号没有参与到最终匹配中,而且处于最后(因此会被忽略☞450)。

还要提一点,尽管现在使用例如「(?P<2>…)」之类的数字命名并不会出错,但这种做法并不可取。PHP4和PHP5在处理非正常情况时会有所区别,可能不会按照个人的意愿发展,所以最好还是不要使用数字来命名捕获分组。

更多的匹配细节:PREG_OFFSET_CAPTURE

如果设置了 preg_match 的第 4 个参数 flags,而且包含 PREG_OFFSET_CAPTURE(这也是preg_match目前能够接受的唯一标志位),则$matches的每个元素不再是普通字符串,而是由两个元素构成的子数组,其中第1 个元素是匹配的文本,第2 个元素是这段文本在目标字符串中的偏移值(如果没有参与匹配,则为-1)。

偏移值从0开始,表示这段文本相对目标字符串的偏移值,即使设置了第5个参数$offset,偏移值的计算也不会变化。它们通常按照字节来计数,即使使用了模式修饰符 u 也是如此(☞447)。

来看个从tag中提取HREF属性的例子。HTML的属性值两边可能是双引号、单引号,或者干脆没有引号,这样的值在下面这个正则表达式的第1组、第2组和第3组捕获型括号中被捕获:

如果$tag包含:

<a name=bloglink href='http://regex.info/blog/'rel="nofollow">匹配成功之后,$matches的内容是:

$matches[0][0]包含正则表达式匹配的所有文本,$matches[0][1]表示匹配文本在目标字符串中的偏移值,按字节计数。

为了清晰起见,另一种获得$matches[0][0]的办法是:

substr($tag,$matches[0][1],strlen($matches[0][0]));

$matches[1][1]是-1,表示第1组捕获括号没有参与匹配。第3组也没有参与,但是因为之前提到的理由(☞450),结尾未参与匹配的捕获括号匹配的文本不会包含在$matches中。

offset参数

如果 preg_match中设置了 offset 参数,引擎会从目标字符串的对应位置开始(如果 offset是负数,则从字符串的末尾开始倒数)。默认情况下,offset是0(也就是说,从目标字符串的开头开始)。

请注意,offset是按字节计数的,即使使用了模式修饰符u也是这样。如果设置不正确(例如从某个多字节字符的“内部”开始)会导致匹配失败。

即使offset不等于0,PHP也不会把这个位置标记为「^」——字符串的起始位置,它只表示正则引擎开始尝试的位置。不过,逆序环视倒是可以检查offset左边的文本。

preg_match_all

使用方法

preg_match_all(pattern,subject,matches [,flags [,offset]])

参数简介

pattern分隔符包围起来的正则表达式,可能出现修饰符(☞444)。

subject需要检索的目标字符串。

matches 用来保存匹配数据的变量(必须出现)。

flags非强制出现,标志位设定整个函数的功能:

PREG_OFFSET_CAPTURE(☞456)

和/或任意:

PREG_PATTERN_ORDER(☞455) PREG_SET_ORDER(☞456)

offset非强制出现,从0开始,表示目标字符串中匹配尝试开始的位置(与preg_match的offset参数相等☞453)。

返回值

preg_match_all返回匹配的次数。

讲解

preg_match_all类似于preg_match,只是在找到第一个匹配之后,它会继续搜索字符串,找到其他的匹配。每个匹配都会创建一个包含匹配数据的数组,所以最后 matches变量就是一个二维数组,其中的每个子数组对应一次匹配。

这里有个简单的例子:

preg_match_all要求必须出现第3个参数(也就是用来收集所有成功的匹配信息的变量)。所以,这个例子中虽然没有用到$all_matches,但仍然必须设置这个变量。

收集匹配数据

preg_match和 preg_match_all的另一个主要区别是第3 个参数中的数据。preg_match进行至多一次匹配,所以它把匹配的数据存储在matches变量中。与此不同的是,preg_match_all能匹配许多次,所以它的第3 个参数保存了多个单次匹配的 matches。为了说明这种区别,我使用$all_matches作为preg_match_all的变量名,而不是$preg_match中常用的$matches。

preg_match_all可以以两种方式在$all_matches中存放数据,根据下面两个互斥的第4个参数flag:PREG_PATTERN_ORDER或是PREG_SET_ORDER来决定。

默认的排列方式是 PREG_PATTERN_ORDER 下面有个例子(我称其为“按分组编号的(collated)”——稍后将介绍)。如果没有设置标志位,这就是默认的配列方式:

$all_matches的结果为:

一共匹配了两次,每次都包含一个“全局匹配”字符串,以及 3 个捕获型括号对应的子字符串。我称其为“按分组编号的(collated)”,因为所有的全局匹配都存放在一个数组里(在$all_matches[0],每次匹配中,第1组括号配的文本存放在另一个数组$all_matches[1]中,依次类推。

默认情况下,$all_matches是按分组编号的,但我们可设置PREG_SET_ORDER来改变它。PREG_SET_ORDER 排列方式 如果设定了 PREG_SET_ORDER 标志位,就会采用“堆叠(stacked)”的排列方式。它会把第 1 次匹配的所有数据保存在$all_matches[0] 中,第 2次匹配的所有数据保存在$all_matches[1] 中,依次类推。这就是我们检索字符串的顺序,把每次成功匹配的$matches放进$all_matches数组中。

下面是之前那个例子的PREG_SET_ORDER的版本:

结果是:

两种排列方式的总结如下:

preg_match_all和PREG_OFFSET_CAPTURE标志位

就像 preg_match 一样,也可以在 preg_match_all 中使用 PREG_OFFSET_CAPTURE,让$all_matches的每个末端元素(leaf element)成为一个两个元素的数组(匹配的文本,以及按字节计算的偏移值)。也就是说,$all_matches 成为一个数组的数组的数组,这可真饶舌。如果你希望同时使用PREG_OFFSET_CAPTURE和PREG_SET_ORDER,请使用逻辑运算符“or”来连接:

preg_match_all与命名分组

如果使用了命名分组,$all_matches将会多出命名元素(同preg_match一样☞451)。这段程序:

$all_matches的结果是:

如果使用PREG_SET_ORDER:

结果就是:

我个人认为,在使用了命名分组之后,就应该去掉数字编号,因为这样程序更清晰,效率更高,不过,如果它们被保留了,你可以当它们不存在。

preg_replace

使用方法

preg_replace(pattern,replacement,subject [,limit [,count]])

参数简介

pattern分隔符包围起来的正则表达式,可能出现修饰符。pattern也可能是一个pattern-argument字符串的数组。

replacement replacement字符串,如果pattern是一个数组,则replacement是包含多个字符串的数组。如果使用了模式修饰符 e,则字符串(或者是数组中的字符串)会被当作PHP代码(☞459)。

subject需要搜索的目标字符串。也可能是字符串数组(按顺序依次处理)。

limit非强制出现,是一个整数,表示替换发生的上限(☞460)。

count非强制出现,用来保存实际进行的替换次数(只有PHP5提供,☞460)。

返回值

如果 subject 是单个字符串,则返回值也是一个字符串(subject 的副本,可能经过修改)。

如果subject是字符串数组,返回值也是数组(包含subject的副本,可能经过修改)。

讲解

PHP 提供了许多对文本进行查找-替换的办法。如果查找部分可以用普通的字符串描述,str_replace或者 str_ireplace就更合适,但是如果查找比较复杂,就应该使用 preg_replace。

来看一个简单的例子:在Web开发中经常会遇到这样的任务,把信用卡号或电话号码输入一张表单。你是否经常看到“不要输入空格和连字符”的提示?要求用户按规则输入数据,还是由程序员做一点小小的改进,让用户可以照自己的习惯输入数据?哪种办法更好(注3)?毕竟,这里我们的要求就是“清理”这样的输入数据:

其中用 preg_replace 来去掉非数字字符。更确切的说,它用 preg_replace 来生成$card_number 的副本,将其中的非数字字符替换为空(空字符串),把这个经过修改的副本赋值给$card_number。

单字符串,单替换规则的preg_replace

前面三个元素(pattern,replacement 和 subject)都是既可以为字符串,也可以为字符串数组。通常这三者都是普通的字符串,preg_replace 首先生成 subject 的副本,在其中找到pattern的第1次匹配,将匹配的文本替换为replacement,然后重复这一过程,直到搜索到字符串的末尾。

在replacement字符串中,‘$0’表示匹配的所有文本,‘$1’表示第1组捕获型括号匹配的文本,‘$2’表示第2组,依次类推。请注意,美元符加数字的字符序列并不会引用变量,虽然它们在其他某些语言中有这种功能,但是 preg_replace 能识别简单的序列,并进行特殊处理。你可以使用一对花括号来包围数字,比如‘${0}’和‘${1}’,这样就不会引起混淆。

这个简单的例子把HTML的bold tag转换为全部大写:

$html=preg_replace('//b[A-Z]{2,}/b/','<b>$0</b>',$html);

如果使用了模式修饰符e(它只能出现在preg_replace中),replacement字符串会作为PHP代码,每次匹配时执行,结果作为replacement字符串。下面这个扩展的例子把bold tag里的单词变为小写:

如果正则表达式匹配的单词是‘HEY’,replacement字符串中的$0会被替换为这个值。结果,replacement字符串就成了‘strtolower("<b>HEY</b>")’,执行这段PHP代码,结果就是‘<b>hey</b>’。

如果使用模式修饰符 e,replacement 字符串中的捕获引用会按照特殊的规定来插值:插值中的引号(单引号或双引号)会转义。如果不这样处理,插入的数值中的引号会导致 PHP代码出错。

如果使用模式修饰符e,在replacement字符串中引用外部变量,最好是在replacement字符串文本中使用单引号,这样变量就不会进行错误的插值。

这个例子类似于PHP内建的htmlspecialchars()函数:

$new_subject=preg_replace('/[&<">]/eS','$replacement["$0"]',$subject);需要注意,这个例子中的replacement使用了单引号字符串来避免$replacement变量插值,直到将其作为PHP代码执行。如果使用双引号字符串,在传递给preg_replace之前,插值就会进行。

可以用模式修饰符S用来提高效率(☞478)。

preg_replace 的第 4 个参数用来设定替换操作次数的上限(单位是单个字符串-单个正则表达式,参见下一节)。默认值是-1,表示“没有限制”。

如果设置了第5个参数count(PHP4没有提供),它会用来保存preg_replace的实际替换的次数。如果你希望知道是否发生了替换,可以比较原来的目标字符串和结果,不过检查count参数效率更高。

多字符串,多替换规则

前一节已经提到,目标字符串通常是普通字符串,至少我们目前看到的所有例子都是如此。不过,subject 也可以是一个字符串数组,这样搜索和替换是对每个字符串依次进行的。返回值也是由每个字符串经过搜索和替换之后的数组。

无论使用的是字符串还是字符串数组,pattern和replacement参数也可以是字符串数组,下面是各种组合及其意义:

如果subject参数是数组,则依次处理数组中的每个元素,返回值也是字符串数组。

请注意limit参数是以单个pattern和单个subject为单位的。它不是对所有的pattern和subject生效。返回的$count则是所有pattern和subject字符串所进行操作次数的总合。

这里有一个preg_replace的例子,其中pattern和replacement都是数组。其结果类似于PHP内建的htmlspecialchars()函数,它保证处理过的文本符合HTML规范:

如果输入的文本是:

AT&T--> "baby Bells"

$cooked的值就是:

AT&T-->"baby Bells"

当然也可以预先准备好这些数组,下面的程序运行结果相同:

preg_replace 能够接收数组作为参数是很方便的(这样程序员就不需要使用循环在各个pattern和subject中进行迭代),但是它的功能并没有增强。比如,各个pattern并不是“并行”处理的。但是,相比自己写PHP循环代码,内建的处理效率更高,而且更容易阅读。为了说清楚,请参考这个例子,其中所有的参数都是数组:

$result_array=preg_replace($regex_array,$replace_array,$subject_array);

它等价于:

数组参数的排序问题 如果pattern和replacement都是字符串,它们会根据数组的内部顺序配对,这种顺序通常就是它们添加到数组中的先后顺序(pattern 数组中添加的第1 个元素对应replacement 数组中的第1 个元素,依次类推)。也就是说,对于 array()创建的“文本数组”来说,排序没有问题,例如:

「[a-z]+」对应‘word<$0>’,下面的「/d+」对应‘num<$0>’,结果就是

result:word<this> word<has> num<7> word<words> word<and> num<31> word<letters>相反,如果 pattern 或 replacement 数组是多次填充的,数组的内部顺序可能就不同于 keys的顺序(也就是说,由 keys 表示的数字顺序)。所以前一页的程序使用数组模拟preg_replacement的程序要使用each来按照数组的内部顺序遍历整个数组,而不关心它们的keys如何。

如果 pattern 或 replacement 数组的内部顺序不同于你希望匹配的顺序,可以使用 ksort()函数来确保每个数组的实际顺序和外表顺序是相同的。

如果pattern和replacement都是数组,而pattern中元素的数目多于replacement中的元素,则会在replacement数组中产生对应的空字符串,来进行配对。

pattern 数组中的元素顺序不同,结果可能大不相同,因为它们是按照数组中的顺序来处理的。如果把例子中的pattern数组的顺序颠倒过来(把replacement数组中的顺序也颠倒过来),结果是什么呢?也就是说,下面代码的结果是什么呢?

ϖ 请翻到下页查看答案。

preg_replace_callback

使用方法

preg_replace_callback(pattern,callback,subject [,limit [,count]])参数简介

pattern 分隔符包围起来的正则表达式,可能出现修饰符(☞444)。也可能是字符串数组。

callback PHP回调函数,每次匹配成功,就执行它,生成replacement字符串。

subject 需要搜索的目标字符串。也可能是字符串数组(依次处理)。

limit非强制出现,设定替换操作的上限(☞460)。

count非强制出现,用来保存实际发生替换的次数(只在PHP 5.1.0中提供)。

返回值

如果subject是字符串,返回值就是字符串(其实是subject的一个副本,可能经过了修改)。如果subject是字符串数组,返回值就是数组(每个元素都是subject中对应元素的副本,可能发生了修改)。

讲解

preg_replace_callback类似于 preg_replace,只是replacement 参数变成了PHP 回调函数,而不是字符串或是字符串数组。它有点像使用模式修饰符e的preg_replace(☞459),但是效率更高(如果replacement部分的代码很复杂,这种办法更易于阅读)。

请参考PHP文档获得更多关于回调的知识,不过简单地说,PHP回调引用(以许多种方式中的一种)一个预先规定的函数,以预先规定的参数,返回预先规定的值。在preg_replace_callback 中,每次成功匹配之后都会进行这种调用,参数是$matches 数组。函数的返回值用作preg_replace_callback作为replacement。

回调可以以三种方式引用函数。一种是直接以字符串形式给出函数名;另一种是用PHP内建的create_function生成一个匿名函数。稍后我们会看到使用这两种方法的例子。第三种方式本书没有提及,它采用面向对象的方式,由一个包含两个元素(分别是类名和方法名)的数组构成。

下面这个例子用preg_replace_callback和辅助函数重写了第460页的程序。callback参数是一个字符串,包含辅助函数的名字:

如果$subject的值是:

"AT&T" sounds like "ATNT"

则$new_subject的值就是:

"AT&T"sounds like"ATNT"

本例中的text2html_callback是普通的PHP函数,用作preg_replace_callback中的回调函数,它的接收参数是$matches数组(当然,这个变量可以随意命名,不过我选择遵循之前使用$matches的惯例)。

为完整起见,下面我给出使用匿名函数的办法(使用PHP内建的create_function函数)。这段程序产生的$replacement变量与上面一样。函数体也相同,只是此时函数没有名字,只能在preg_replace_callback中使用:

使用callback,还是模式修饰符e

如果处理不复杂,使用模式修饰符的程序比preg_replace_callback更容易看懂。但是,如果效率很重要,那么请记住,如果使用模式修饰符e,每次匹配成功之后都需要检查作为PHP代码的replacement参数。相比之下,preg_replace_callback的效率就要高许多(如果使用回调,PHP代码只需要审查1次)。

preg_split

使用方法

preg_split(pattern,subject [,limit,[flags]])

参数简介

pattern 分隔符包围起来的正则表达式,可能还有修饰符(☞444)。

subject 需要分割的目标字符串。

limit非强制出现,是一个整数,表示切分之后元素的上限。

flags非强制出现,此标志位影响整个切割行为,以下三项可以随意组合:

它们的讲解从第468页开始。多个标志位使用二元运算符“或”来连接(与第456页一样)。

返回值

返回一个字符串数组。

讲解

preg_split会把字符串的副本切分为多个片段,以数组的形式返回。非强制出现参数limit设定返回数组中元素数目的上限(如果需要,最后的元素包括“其他所有字符”)。可以设定不同的标志位来调整返回的方式和内容。

从某种意义上来说,preg_split做的是与preg_match_all相反的事情:它找出目标字符串中不能由正则表达式匹配的部分。或者更传统地说,preg_split返回的是,将目标字符串中正则表达式匹配的部分删去之后的部分。preg_split 大概相当于 PHP 中内建的简单explode函数,不过使用的是正则表达式,而且功能更强大。

来看个简单的例子,如果某家金融网站需要接收用空格分隔的股票行情。可以使用explode拆分这些行情数据:

$tickers=explode('',$input);

不过,如果输入数据时不小心输入了不只一个空格,这个程序就不能处理了。更好的办法是使用preg_split,用正则表达式「/s+」来切分:

$tickers=preg_split('//s+/',$input);

除了明确运用“用空格切分”的规则之外,用户也通常使用逗号(或者是逗号加空格)来分隔,比如‘YHOO,MSFT,GOOG’。这些情况也很容易处理:

$tickers=preg_split('/[/s,]+/',$input);

针对上面的数据,$tickers得到的是包含3个元素的数组:‘YHOO’、‘MSFT’和‘GOOG’。

如果输入的数据是逗号分隔的(例如给照片标记 tag 时使用的“Web 2.0,”),就需要用「/s*,/s*」来处理:

$tags=preg_split('//s*,/s*/',$input);

比较「/s*,/s*」和「[/s,]+」很能说明问题。前者用逗号来切分(逗号必须出现),但也会删去逗号两边的空白字符。如果输入‘123,,,456’,则能够进行3次匹配(每次匹配一个逗号),返回4个元素:‘123’,两个空字符串,最后是‘456’。

另一方面,「[/s,]+」会使用任何逗号、连续的逗号、空白字符,或者是空白字符和逗号的结合来切分。在‘123,,,456’中,它一次就能匹配3个逗号,返回两个元素,‘123’和‘456’。

limit参数

limit 参数用来设定切分之后数组长度的上限。如果搜索尚未进行到字符串结尾时,切分的片段的数目已经达到limit,则之后的内容会全部保存到最后的元素当中。

来看个例子,我们需要手工解析服务器返回的 HTTP response。按照标准,header 和 body的分隔是四字符序列‘/r/n/r/n’,不幸的是,有的服务器使用的却是‘/n/n’。幸好,我们有preg_split,很容易处理这两种情况。假设整个response保存在$response中:

$parts=preg_split('//r?/n/r?/n/x',$response,2);

header保存在$parts[0]中,而body保存在$parts[1]中(使用模式修饰符S是为了提高效率☞478)。

第3个参数,即limit的值等于2,表示subject字符串最多只能切分成两个部分。如果找到了一个匹配,匹配之前的部分(也就是 header)会成为返回值的第一个元素。因为“字符串的其他部分”是第2个元素,这样就到达了上限,它(我们知道是body)会原封不动地作为返回值中的第二个元素。

如果没有limit(或者limit等于-1,这两种情况是等价的),preg_split会尽可能多地切分subject字符串,这样body可能也会被切分为许多段。设置上限并不能保证返回的数组中包含的元素就等于这个数,而只是保证最多包含这么多元素(阅读关于PREG_SPLIT_DELIM_CAPTURE的小节,你会发现甚至这种说法也不完全对)。

在两种情况下,应该人为设置上限。我们已经见过一种情况:希望最后的元素包含“其他所有内容”。在前一个例子中,一旦第一段(header)被切分出来,我们就不希望再对其他部分(body)进行切分。所以,把上限设为2会保留body。

如果用户知道自己不需要切割出来所有元素,也可以设定上限,提高效率。例如,如果$data字符串包含以「/s*,/s*」分隔的许多字段(比如姓名、地址、年龄,等等),而只需要前面两个,就可以把limit 设置为3,这样 preg_split在切分出前两个字段之后就不会继续工作:

$fields=preg_split('//s*,/s*/x',$data,3);

这样其他内容都保存在最后的第3个元素中,我们可以用array_pop来删除,或者置之不理。

如果你希望在没有设置上限的情况下使用任何preg_split标志位(下一节讨论),则必须提供一个占位符,将limit设置为-1,它表示“没有限制”。相反,如果limit等于 1,则表示“不需要切分”,所以它并不常用。上限等于0或者-1之外的任何负数都没有定义,所以请不要使用它们。

flag参数

preg_split中可以使用的3个标志位都会影响函数的功能。它们可以单独使用,也可以用二元运算符“or”连接(参见第456页的例子)。

PREG_SPLIT_OFFSET_CAPTURE 就像在 preg_match 和 preg_match_all 中使用 PREG_OFFSET_CAPTURE一样,这个标志位会修改结果数组,把每个元素变为包含两个元素的数组(元素本身和它在字符串中的偏移值)。

PREG_SPLIT_NO_EMPTY 这个标志位告诉preg_split忽略空字符串,不把它们放在返回数组中,也不记入limit的统计。对目标字符串的起始位置、结尾位置,或是空行的匹配,都会带来空字符串。

下面来改进前面的“Web 2.0”的tag的例子(☞466),如果$input为‘party,,fun’,那么:

$tags=preg_split('//s*,/s*/x',$input);

得到的$tags包含3个元素:‘party’、空字符串,然后是‘fun’。空字符串是逗号的两次匹配之间的“空白”。

如果设置了PREG_SPLIT_NO_EMPTY 标志位:

$tags=preg_split('//s*,/s*/x',$input,-1,PREG_SPLIT_NO_EMPTY);

结果数组只包含‘party’和‘fun’。

PREG_SPLIT_DELIM_CAPTURE 这个标志位在结果中包含匹配的文本,以及进行此次切分的正则表达式的捕获括号匹配的文本。来看个简单的例子,如果字符串中各个字段是以‘and’和‘or’来联系的,例如:

DLSR camera and Nikon D200 or Canon EOS 30D

如果不使用PREG_SPLIT_DELIM_CAPTURE,

$parts=preg_split('//s+(and|or)/s+/x',$input);

得到的$parts是:

array ('DLSR camera','Nikon D200','Canon EOS 30D')

分隔符中的匹配内容被去掉了。不过,如果使用了PRE_SPLIT_DELIM_CAPTURE 标志位(并且用-1作为limit参数的占位符):

$parts包含了捕获型括号匹配的分隔符:

array ('DLSR camera','and','Nikon D200','or','Canon EOS 30D')

此时,每次切分会在结果数组中增加一个元素,因为正则表达式中只有一组捕获型括号。然后我们就能够遍历$parts中的元素,对找到的‘and’和‘or’进行特殊处理。

请注意,如果使用了非捕获型括号(如果 pattern 参数为‘//s+(?:and|or)/s+/’),PREG_SPLIT_DELIM_CAPTURE 标志位不会产生任何效果,因为它只对捕获型括号有效。

来看另一个例子,第466页分析股市行情的例子:

$tickers=preg_split('/[/s,]+/',$input);

如果我们添加捕获型括号,以及PREG_SPLIT_DELIM_CAPTURE

$tickers=preg_split('/([/s,]+)/',$input,-1,PREG_SPLIT_DELIM_CAPTURE);结果$input 中的任何字符都没有被抛弃,它只是切分之后保存在$tickers 中。处理$tickers数组时,你知道编号为奇数的元素是「([/s,]+)」匹配的。这可能很有用,如果在向用户显示错误信息时,可以对不同的部分分别进行处理,然后将它们合并起来,还原出输入的字符串。

还有一点需要注意,通过PREG_SPLIT_DELIM_CAPTURE添加的元素不会影响切分上限。只有在这种情况下,结果数组中的元素数目才可能超过上限(如果正则表达式中的捕获型括号很多,则元素就要更多)。

结尾的未参与匹配的捕获型括号不会影响结果数组。也就是说,如果一组捕获型括号没有参与最终匹配(参见450页),可能会也可能不会在结果数组中添加空字符串。如果编号更靠后的捕获型括号参与了最终匹配,就会增加,否则就不会。请注意,如果使用了PREG_SPLIT_NO_EMTPY 标志位,结果会有变化,因为空字符串肯定会被抛弃。

preg_grep

使用方法

preg_grep(pattern,input[,flags])

参数简介

pattern 分隔符包围起来的正则表达式,可能出现修饰符。

input 一个数组,如果它们的值能够匹配pattern,则其值会复制到返回的数组中。

flags 非强制出现,此标志位PREG_GREP_INVERT或者是0。

返回值

一个数组,包含input中能够由pattern匹配的元素(如果使用了 PREG_GREP_INVERT标志位,则包括不能匹配的元素)。

讲解

preg_grep 用来生成 input 数组的副本,其中只保留了 value 能够匹配(如果使用了PREG_GREP_INVERT标志位,则不能匹配)pattern的元素。此value对应的key会保留。来看个简单的例子

preg_grep('//s/',$input);

它返回$input数组中的,由空白字符构成的元素。相反的例子是:

preg_grep('//s/',$input,PREG_GREP_INVERT);

它返回不包含空白字符的元素。请注意,第二个例子不同于:

preg_grep('/^/S+$/',$input);

因为后者不包括空(长度为0)值元素。

preg_quote

使用方法

preg_quote(input [,delimiter])

参数简介

input希望以文字方式用作pattern参数的字符串(☞444)。

delimiter 非强制出现的参数,包含1个字符的字符串,表示希望用在pattern参数中的分隔符。

返回值

preg_quote返回一个字符串,它是input 的副本,其中的正则表达式元字符进行了转义。如果指定了分隔符,则分隔符本身也会被转义。

讲解

如果要在正则表达式中以文字方式使用某个字符串,可以用内建的preg_quote函数来转义其中可能产生的正则表达式元字符。如果指定了创建pattern时使用的分隔符,字符串中的分隔符也会被转义。

preg_quote是专门应对特殊情况的函数,在许多情况下没有用,不过这里有个例子:

如果$MailSubject包含下面的字符串

**Super Deal** (Act Now!)

最后$pattern就会是

/^Subject:/s+(Re:/s*)*/*/*Super Deal/*/*/(Act Now/!/)/mi

这样就可以作为preg函数的pattern参数了。

如果指定的分隔符是‘{’之类对称的字符,那么对应的字符(例如‘}’)不会转义,所以请务必使用非对称的分隔符。

同样,空白字符和‘#’也不会转义,所以结果可能不适于用x修饰符。

这样说来,在把任意文本转换为 PHP 正则表达式的问题上,preg_quote 并不是完善的解决办法。它只解决了“文本到正则表达式”的问题,而没有解决“正则表达式到pattern参数”的问题,任何preg函数都需要这一步。下一节给出了解决办法。