模式 匹配是软件中最常见的内容,因此正则表达式这种特殊的简写方式得到了不断演变,从而实现任务简化。在 “掌 握 PHP 中的正则表达式 ” 系列的第 1 部分中了解如何在代码中使用这种简写方式。
所有机器都会消耗输入,执行某种工作,然后生成输出。例如,电话把声能转换为电信号并重新转换回声频来启动对话。发动机吸 收燃料(蒸汽、裂变、汽油或者做大量的功)并将其转换为功。又或者将朗姆酒、冰块、酸橙和柑桂酒倒入调酒壶中,并且用力搅拌制作麦泰(或者,如果您希望调 制出更具有大都会特色的饮品,请尝试使用一点香槟酒和带果肉的梨汁饮料来享用贝利尼。调酒壶真是一个灵活而又非凡的工具)。
由于软件将转换数据,因此每个应用程序也是一台机器 —— 但是是一台 “虚拟” 机器,因为没有使用物理部件。例如,编译器期望获得源代码作为输入并将其转化为适于执行的二进制代码。气象建模工具将根据历史测量数据来生成预报。而图像 编辑器将消耗并生成像素,对每个像素或每组像素应用规则从而锐化图像或形成某种风格。
就像任何其他机器一样,软件应用程序期望获得某些 “原料”,例如数字列表、XML 模式中封装的数据或者协议。如果给程序提供错误的 “原料” —— 类型或形式不匹配 —— 则很可能得到无法预测的结果,甚至导致灾难。有句格言说得好,“错误的输入必然导致错误的输出”。
事实上,所有比较重要的问题都要求从错误数据中过滤出正确数据和/或拒绝错误数据以防止得到错误输出。对于 PHP Web 应用程序来说也是如此。无论输入是来自手动形式还是来自编程式的 Asynchronous JavaScript + XML (Ajax) 请求,程序都必须在执行任何计算之前检查传入信息。可能要求数值属于某个范围或者被限定为整数。值可能需要匹配一种特定格式,如邮政编码。例如,美国的邮 政编码是五位数字加上可选的 “4 个” 限定号码,后者由连字符和四位附加数字组成。其他字符串可能是特定数目的字符,例如两个字母表示的美国各州的缩写。字符串最为棘手:PHP 应用程序必须对嵌入 SQL 查询、JavaScript 代码或者其他能够改变应用程序行为或有碍安全性的恶意操作程序保持警惕。
但是程序如何告知输入是数字还是遵循某个约定(例如邮政编码)?基本上,执行匹配需要使用一个小型解析器 —— 创建一个状态机、读取输入、处理标记、监视状态并生成结果。但是,即使是一个简单的解析器,也难于进行创建和维护。
幸运的是,由于模式匹配分析是最常见的计算需求,因此,随着时间的推移,一种特殊的简写方式(引擎)应运而生(大约从 UNIX® 出现之后),它可以减轻事务的工作量。正则表达式 (regex) 使用简明、易读的符号描述模式。给定一个正则表达式和数据,正则表达式引擎将得到数据是否匹配模式及匹配内容(如果找到匹配)等结果。
下面是应用从 UNIX 命令行实用程序 grep 中提取的正则表达式的简单示例,该实用程序将在一个或多个 UNIX 文本文件的内容中搜索指定模式。命令 grep -i -E '^Bat'
将搜索序列 beginning-of-line
(用 脱字符号 [^] 来表示),后面紧接着大写或小写字母 b、a 和 t (使用 -i
选项将在模式匹配时忽略大小写,举例来说,也就是 B 和 b 是等效的)。因此,给出文件 heroes.txt:
|
上述 grep 命令将生成两个匹配:
Batman |
PHP 将提供两个 regex 编程接口,一个用于可移植操作系统接口(Portable Operating System Interface,POSIX),另一个接口用于 Perl Compatible Regular Expressions (PCRE)。基本上,推荐使用第二个接口,因为 PCRE 比 POSIX 实现更加强大,可以提供能在 Perl 中找到的所有操作符。要了解关于 POSIX regex 函数调用的更多信息,请阅读 PHP 文档(请参阅 参 考资料 )。在这里,我主要介绍 PCRE 功能。
PHP PCRE regex 包含针对特定字符和其他操作符、针对特定位置(例如字符串的开头或结尾)或者针对单词的开头或结尾的匹配操作符。regex 还可以描述替代词,即在其他技术中可能描述为 “this” 或 “that” 的单词;定长、变长或不确定长度的副本;字符集(例如,“a 到 m 之间的任意字母”);以及类 或各类字符(可打印字符或标点符号)。regex 中的特殊操作符也允许分组 —— 一种将某个操作符应用到所有其他操作符的方法。
表 1 显示了一些常用的 regex 操作符。您可以连接和结合表 1 中的基本操作符(以及其他操作符)并进行组合来构建(非常)复杂的 regex。
操作符 | 用途 |
---|---|
.(句点) | 匹配所有单个字符 |
^(脱字符号) | 匹配出现在行或字符串开头的空字符 串 |
$(美元符号) | 匹 配出现在行尾的空字符串 |
A | 匹 配大写字母 A |
a | 匹配小写字母 a |
/d | 匹配所有一位数字 |
/D | 匹配所有单个非数字字符 |
/w | 匹配所有单个字母或数字字符;同义词是 [:alnum:] |
[A-E] | 匹配所有大写的 A、B、C、D 或 E |
[^A-E] | 匹配除 大写 A、B、C、D 或 E 之外的任何字符 |
X? | 匹 配出现零次或一次的大写字母 X |
X* | 匹配零个或多个大写字母 X |
X+ | 匹配一个或多个大写字母 X |
X{n} | 精确匹配 n 个大写字母 X |
X{n,m} | 至少匹配 n 个且不多于 m 个大写字母 X ; 如果忽略 m ,则表达式将尝试匹配至少 n 个 X |
(abc|def)+ | 匹配一连串的(最少一个)abc 和 def ;abc 和 def 将匹配 |
下面是 regex 的常见用法示例。假定 Web 站点要求每个用户创建一个登录名。每个用户名至少要包含 3 个但不多于 10 个字母数字字符,并且必须以字母为开头。要强制遵守这些规范,可以使用以下 regex 在提交给应用程序时验证用户名:^[A-Za-z][A-Za-z0-9_]{2,9}$
。
脱字符号将匹配字符串的开头。第一个集合 [A-Za-z]
表示所有字母。第二个集合 [A-Za-z0-9_]{2,9}
表示由至少 2 个至多 9 个任意字母、数字和划线组成的序列。并且使用美元符号 ($
) 匹配字符串末尾。
乍看之下,美元符号可能看似不必要,但是它是至关重要的。如果忽略掉它,regex 将匹配开头为字母、包含 2 至 9 个字母数字字符以及任意数目的任何其他字符的所有字符串。换言之,没有美元符号锚定字符串的结尾,带有匹配前缀(例如 “martin1234-cruft”)的非常长的字符串将生成误判 (false positive)。.
PHP 提供了用于在文本中查找匹配、将每个匹配替换为其他文本(la 搜索和替换)以及在列表的元素之中查找匹配的函数。函数包括:
-
preg_match()
-
preg_match_all()
-
preg_replace()
-
preg_replace_callback()
-
preg_grep()
-
preg_split()
-
preg_last_error()
-
preg_quote()
为了演示函数,让我们编写一个小型 PHP 应用程序,该应用程序将搜索单词列表以查找特定模式,遵循这种模式的单词和 regex 都是由传统的 Web 表单来提供的,并且结果都使用 simple print_r()
函数返回给浏览器。如果需要测试或改进 regex,则这种小型程序非常有用。
清单 2 显示了 PHP 代码。所有输入都是通过简单的 HTML 表单来提供的(为了简洁起见,不显示相应的表单,并且已经省略了用于捕捉 PHP 代码错误的代码)。