设为首页 收藏本站
查看: 673|回复: 0

[经验分享] 掌握 PHP 中的正则表达式,第 2 部分: 如何在 PHP 中处理文本

[复制链接]

尚未签到

发表于 2017-4-5 10:12:00 | 显示全部楼层 |阅读模式
  原文地址
  http://www.ibm.com/developerworks/cn/opensource/os-php-regex2/index.html
  2008 年  3 月  09 日

本文是 “掌
握 PHP 中的正则表达式
” 系列的第 2 部分,通过本文您将了解如何用一些高级正则表达式 (regex)
操作符来解决各种复杂的文本处理问题。
  
<!-- START RESERVED FOR FUTURE USE INCLUDE FILES--><!--  include java script once we verify teams wants to use this and it will work on dbcs and cyrillic characters -->
<!-- END RESERVED FOR FUTURE USE INCLUDE FILES-->

  虽然术语数据
信息
可以互换使用,但是两者之间有很大的差别。数据是有据可依的。温度列表、近期销售状况说明或者库存零部件清单,这些都是数据。信息含有一定见解。天气预
报、损益表和销售趋势属于信息。数据是由若干个 1 和 0 表示,而信息则经由人脑分析得出。
  数据和信息之间是软件应用程序:引擎将把数据和信息来回转换。例如,如果在线购买图书,购书应用程序将把信息 ——
书名、身份信息、银行帐号信息 ——
转换为数据,例如订单号、售价、信用卡交易详细信息和对存货清单的调整。类似地,购书应用程序将把数据再转换为仓库提货请求、运输标签和跟踪编号等完成销
售所需的信息。
  当然,创建应用程序的复杂度与其影响的转换直接成正比。Web
站点留言本十分简单,它把姓名和地址转换为数据库中的字段。同时,在线商店十分复杂,它将把各类信息转换为业务数据模型并把数据转换为信息来推动决策。编
程的艺术在于对数据和信息的熟练处理 —— 类似于在明暗处理中捕捉亮色的技能。
  如 第 1
部分
中所述,regex 是处理数据的最强大工具之一。使用简明的简写方式,regex
说明了数据的格式并分解数据。例如,您可以使用下面的 regex 处理所有摄氏或华氏温度:/^([+-]?[0-9]+)([CF])$/

  regex 将匹配行的开头(由脱字符号 ^
表示),后接一个正号,一个负号,或者两者都不是 ([+-]?
),后接一个整数 ([0-9]+
),
数值范围限定符 —— 摄氏或华氏 ([CF]
) —— 并在行尾(用美元符号 $
表示)终止。
  在温度 regex 中,行开头和行结尾操作符是两个零宽度断言
示例,或者匹配位置而非文字。括号也不是文字。相反,嵌入到括号内的模式将捕捉匹配模式的文本。因此,如果文本匹配了整个模式,第一组括号将生成表示一个
正整数或负整数的的字符串,例如 +49
。第二组括号将生成字母 C
F


  第 1 部分介绍了 regex 的概念和可用于比较文本与模式和提取匹配的 PHP 函数。现在我将更深入地研究
regex 并查看一些高级操作符和处理方法。
  (再次)使用括号

  在大多数情况下,使用一组括号可以定义子模式和捕捉匹配子模式的文本。但是,括号不需要捕捉子模式。正如在复杂的数学公式
中,您可以简单地使用括号来给术语分组。
  下面是一个示例。您能否说出它匹配哪类数据?


/[-a-z0-9]+(?:\.[-a-z0-9]+)*\.(?:com|edu|info)/i

  您可能已经预料到此 regex 将匹配主机名(虽然只在 .com、.edu 和 .info
这几个域中)。差别是添加了 ?:
。子模式限定符 ?:
将禁用捕捉,留下括号来阐明操作的优先次序。例如,在这里,短句 (?:\.[-a-z0-9]+)*
将匹配零个或多个字符串实例(例如 “.ibm”)。类似地,短句 \.(?:com|edu|info)
表示句点,后接字符串 com
、edu
或 info
中的任意一个。
  禁用捕捉可能看似毫无意义,直至您意识到捕捉需要额外的处理。如果代码将处理大量数据,则忽略捕捉可能是有意义的。此外,
如果 regex 特别复杂,禁用某些子模式中的捕捉可以更轻松地提取真正感兴趣的子模式。
  
注:
使用 regex 末尾的 i
修饰语可以使模式内的所有匹配都不区分大小写。因此,子集 a-z
将匹配所有字母,而不区分大小写。
  PHP 将提供其他子模式修饰词。使用第 1 部分中提供的 regex 测试 jig(如 清
单 1
所示),将针对候选字符串 “EDU”、“edu” 和 “Edu” 匹配 regex ((?i)edu)

如果子模式以修饰词 (?i)
为开头,则在子模式中进行匹配不区分大小写。只要子模式结束,区分大小写将被重新启用(将此修饰词与上面的 /
...
/i
修饰词相比较,后者应用于整个模式)。
  

清单 1. 简单的 regex 测试实用程序



               
<?php
//
// divide the comma-separated list into individual words
//   the third parameter, -1, permits a limitless number of matches
//   the fourth parameter, PREG_SPLIT_NO_EMPTY, ignores empty matches
//
$words = preg_split( '/,/',  $_REQUEST[ 'words' ], -1, PREG_SPLIT_NO_EMPTY );
//
// remove the leading and trailing spaces from each element
//
foreach ( $words as $key => $value ) {
$words[ $key ] = trim( $value );
}
//
// find the words that match the regular expression
//
$matches = preg_grep( "/${_REQUEST[ 'regex' ]}/", $words );
print_r( $_REQUEST['regex' ] );
echo( '<br /><br />' );
print_r( $words );
echo( '<br /><br />' );
print_r( $matches );
exit;
?>

  另一个有用的子模式修饰词是 (?x)
。它允许您在子模式中嵌入空白,使 regex
更易读。因而,子模式 ((?x) edu | com | info)
(请注意备用操作符之间的空格,这些空格是为了易读性
而添加的)与 (edu|com|info)
相同。您可以使用全局修饰词 /
...
/x
在整个 regex 中嵌入空白和注释,如下所示:
  

清单 2. 嵌入空白和注释



               
$matches = preg_grep(
"/
[- a-z 0-9]+            # machine name
(?: \. [- a-z 0-9]+)*   # subdomains
\. (?: com | edu | info)# domain
/xi", $words );

  正如您所见,还可以根据需要组合修饰词。另外,如果需要在使用 (?x)
时匹配空格,那么,使用元字符 \s
来匹配所有空格字符或使用 \
(反斜杠后接空格)来匹配单
个空格,如 ((?x) hello \ there)



DSC0000.gif








回页首

  其他应用

  regex
的大量应用都是验证或分解存储为存储库中的数据或由应用程序立即执行的各个小块的输入。处理表单中的字段、解析 XML 代码以及解释协议都是典型应用。
  regex 的另一个应用是格式化
、规范化或提高数据的可读性。格式化不是使用 regex
查找和提取文本,而是使用 regex 查找并在正确位置插入文本。
  下面是一个有用的格式化应用程序。假定 Web
表单把按照美元计算的薪金提交给应用程序。由于把薪金存储为整数,因此应用程序必须先去掉所粘贴数据中的标点符号,然后再保存。但是,在从存储库中检索出
数据时,则需要使用逗号重新设定数据的格式使其具有可读性。下面显示了一个用于把美元金额转换为数字的简单 PHP 调用。
  

清单 3. 把美元金额转换为数字



               
$salary = preg_replace( "/[\$\s,]/", '', $_REQUEST[ 'salary' ] );
if ( is_numeric( $salary ) ) {
// persist the data
}
else {
// error
}

  调用 preg_replace()
函数将用空字符串替换美元符号、所有空格和每个逗号,生成认为是整数的内容。如果调用 is_numeric()
对输入进行了验证,则可以存储数据。
  接下来,让我们反向操作输出带有货币符号和用于分隔百、千、百万的逗号的数字。您可以编写代码来查找这些数字单元,也可以
使用向前查找
向后查找
在正确位置上插入逗号。子模式修饰词 ?<=
指示从当前位置开始向后查找
(即向左查找)。修饰词 ?=
表示从当前位置开始向前查找(向右查找)。
  那么,正确位置在哪里?字符串中左侧至少有一位数并且右侧有一组或多组三位数的任意位置,不包括小数点和美分数。给定该规
则和两个查找修饰词(两者都是零宽度断言),这条语句将可成功执行:


$pretty_print = preg_replace( "/(?<=\d)(?=\d\d\d)+$)/", ',', $salary );

  后面的 regex 如何工作?从字符串的开头开始并继续通过每个位置,regex 将断言
“左侧是否至少有一位数并且右侧是否有一组或多组三位数”?如果是这样,逗号将 “替换” 零宽度断言。
  使用类似于上面的策略可以轻松地免除许多复杂匹配。例如,下面是另一种可以轻松解决一般困难的向前查找。
  

清单 4. 向前查找示例



               
$tab_data = preg_replace( '/
,                               # look for a comma
(?=                             # then look ahead for
(?:[^"]*$)                  # a string with no quotes and eol
|                           #  -or-
(?:[^"]*"[^"]*"[^"]*)*$     # a string with balanced quotes
)                               #
/x', "\t", $csv_data );

  这条 preg_replace()
指令将把一行用逗号分隔的数据转换为一行用制表符分隔的数据。它很聪明,不会替换在引号括起的字符串中找到的逗号。
  regex 将在所有出现逗号(这是位于 regex
开头的逗号)的位置做出断言:“前面是不是没有引号或者前面的引号个数是否为偶数”?如果断言为真,则可以用制表符 (\t
)
替换逗号。
  如果不希望使用查找操作符,或者使用的是不提供查找操作符的语言,则可以使用传统 regex
把逗号嵌入到数字中,尽管这样做要求完成多次迭代。下面是一种可能的解决方案。
  

清单 5. 嵌入逗号



               
$pretty_print = preg_replace( "/[\$\s,]/", '', $_REQUEST[ 'salary' ] );
do {
$old = $pretty_print;
$pretty_print = preg_replace( "/(\d)(\d\d\d\b)/", "$1,$2", $pretty_print );
} while ( $old != $pretty_print );

  让我们仔细研究一下代码。首先,移除 salary
参数的标点来模拟从数据库中读取整数。接下来,循环将重复执行,查找这样一个位置:一位数 ((\d)
后接三位数 ((\d\d\d\)
并在 \b
所指定的词界(word boundary)立即终止的位置。词界
是另一个零宽度断言并被定义为:


  • 如果第一个字符为单词字符,则在字符串中的第一个字符之前。
  • 如果最后一个字符为单词字符,则在字符串中的最后一个字符之后。
  • 在单词字符和非单词字符之间,紧跟在单词字符之后。
  • 在非单词字符和单词字符之间,紧跟在非单词字符之后。
  因而,空格、句点和逗号都是有效的词界。
  由于是外部循环,因此 regex
实质上将从右向左前进查找后接三位数和词界的一位数。如果找到匹配,则在两个子模式之间插入一个逗号。只要 preg_replace()
找到匹配,循环就必须继续,这解释了 $old != $pretty_print
条件。











回页首

  贪婪和懒惰

  Regex 十分强大。甚至有时候过于强大。例如,考虑当 regex ".*"
被应用到字符串 “The author of 'Wicked' also wrote 'Mirror, Mirror.'”
上时发生的情况。虽然预期 preg_match()
可能返回两个匹配,但是您可能会惊讶地发现只有一个结果:'Wicked'
also wrote 'Mirror, Mirror.'

  原因是什么?除非进行指定,否则诸如 *
(无或多个)和 +
(一个
或多个)之类的操作符都很贪婪
。如果模式可以继续匹配,那么它可能将生成最多的结果。要使匹配最少,则必须强制使某些操作符变得懒惰

懒惰操作将查找最短的匹配,然后就停止。要使操作符变得懒惰,请添加问号后缀。清单 6 显示了一个示例。
  

清单 6. 添加问号后缀



               
$text = 'The author of "Wicked" also wrote "Mirror, Mirror."';
if ( preg_match_all( '/".*?"/', $text, $matches ) ) {
print_r( $matches[0] );
}

  上面的代码片段将生成:


Array ( [0] => "Wicked" [1] => "Mirror, Mirror." )

  regex ".*?"
变为匹配一个引号,后接刚好足够的
字符,后接一个引号。
  但是,使用 *
操作符有时可能太懒惰。例如,采用以下代码片段。它将生成什么输出?
  

清单 7. 简单的 regex 测试实用程序



               
if (preg_match( "/([0-9]*)/", "-123", $matches  ) ) {
print_r( $matches );
}

  猜测输出是什么?“123”?“1”?没有输出?实际上,输出是 Array ( [0] =>
[1] => )
,表示找到一个匹配,但是未捕捉到任何内容。为什么?回想一下操作符 *
可以匹配零次或多次。在这里,表达式 [0-9]*
针对字符串开头匹配零次,随后停止处理。
  要解决此问题,请添加零宽度断言来锚定匹配,这将强制 regex 引擎继续进行匹配;/([0-9]*\b/
就可解决问题。











回页首

  更多提示和技巧

  regex
可以解决简单或复杂的文本处理问题。首先掌握一些操作符,随着经验逐渐丰富,您可以进一步扩展词汇表。要立即开始使用,请参考下面这些提示和技巧。
  用字符类实现可移植的
regex

  您已经看到过匹配所有空格字符的元字符,例如 \s
。此外,许多 regex
实现都支持更易于跨多种编写语言使用和移植的预定义字符类。例如,字符类 [:punct:]
表示当前语言环境中的所有标点字符。您可以使用 [:digit:]
代替 [0-9]
,并且 [:alpha:]
是比 [-a-zA-Z0-9_]
更具有可移植性的替代者。例如,您可以使用以下语句移除字符串中的所有标点符号:


$clean = preg_replace( "/[[:punct:]]/", '', $string );

  使用字符类比清楚说明所有标点符号更简洁。要获得字符类的完整列表,请参阅适用于您的 PHP 版本的文档。
  排除不需要查找的内容

  与将逗号分隔的值 (CSV) 转换为用制表符分隔的数据一样,列出
需要匹配的内容有时更容易也更精确。以脱字符号 (^
)
为开头的集合将匹配集合中不包括的所有字符。例如,您可以使用正则表达式 /[2-9][0-9]{2}[2-9][0-9]{2}[0-9]{4}/
来验证美国电话号码。使用排除集合,可以把 regex 编写为更显式的 /[^01][0-9]{2}[^01][0-9]{2}[0-9]{4}/

两个 regex 都可以正常运行,但是显然后者意图更加明显。
  跳过换行符

  如果输入跨度多行,则使用典型的 regex 是不够的,因为扫描将在 $
所指示的换行符处终止。但是,如果使用 s
或 m
修饰词,regex
引擎将按照不同的方式处理输入。前者将把字符串处理为单行,强制用点匹配换行符(它通常不这样做)。后者将把字符串处理为多行,其中 ^
和 $
将分别匹配每行的开头和结尾。下面是一个示例:如果设置 $string =
"Hello,\nthere";
,则语句 preg_match( "/.*/s", $string,
$matches)
将把 $matches[0]
设为 Hello,\nthere
(删
除 s
将生成 Hello
)。
  正则表达式几乎无所不能,也许惟一的限制因素就是您的想象力和创造力了。
  参考资料

  




  • 您可以参阅本文在 developerWorks 全球站点上的 英文原文

  • 阅读 “掌
    握 PHP 中的正则表达式
    ” 系列中的其他文章。

  • PHP.net

    PHP 开发者的重要资源。
  • 查阅 “PHP
    推荐读物列表
    ”。
  • 浏览 developerWorks 上的全部 PHP
    文章
    和PHP
    教程

  • 查看 IBM developerWorks 的 PHP
    项目资源
    扩展 PHP 技巧。
  • 收听针对软件开发人员的有趣访谈和讨论,一定要访问 developerWorks podcast


  • 将数据库与 PHP 结合使用?查看 Zend Core for
    IBM
    ,它是一个无缝的、可以立即使用、易于安装、支持 IBM DB2 V9 的 PHP 开发和生产环境。

  • 时关注 developerWorks 的 技术事件和网络广播

  • 查阅最近将
    在全球举办的面向 IBM 开放源码开发人员的研讨会、交易展览、网络广播和其他 活动

  • 访问
    developerWorks 开放源码专区
    ,获得丰富的
    how-to 信息、工具和项目更新,帮助您用开放源码技术进行开发,并与 IBM 产品结合使用。
  • 查看免费的 developerWorks On demand demo
    观看并了解 IBM 及开源技术和产品功能。

运维网声明 1、欢迎大家加入本站运维交流群:群②:261659950 群⑤:202807635 群⑦870801961 群⑧679858003
2、本站所有主题由该帖子作者发表,该帖子作者与运维网享有帖子相关版权
3、所有作品的著作权均归原作者享有,请您和我们一样尊重他人的著作权等合法权益。如果您对作品感到满意,请购买正版
4、禁止制作、复制、发布和传播具有反动、淫秽、色情、暴力、凶杀等内容的信息,一经发现立即删除。若您因此触犯法律,一切后果自负,我们对此不承担任何责任
5、所有资源均系网友上传或者通过网络收集,我们仅提供一个展示、介绍、观摩学习的平台,我们不对其内容的准确性、可靠性、正当性、安全性、合法性等负责,亦不承担任何法律责任
6、所有作品仅供您个人学习、研究或欣赏,不得用于商业或者其他用途,否则,一切后果均由您自己承担,我们对此不承担任何法律责任
7、如涉及侵犯版权等问题,请您及时通知我们,我们将立即采取措施予以解决
8、联系人Email:admin@iyunv.com 网址:www.yunweiku.com

所有资源均系网友上传或者通过网络收集,我们仅提供一个展示、介绍、观摩学习的平台,我们不对其承担任何法律责任,如涉及侵犯版权等问题,请您及时通知我们,我们将立即处理,联系人Email:kefu@iyunv.com,QQ:1061981298 本贴地址:https://www.yunweiku.com/thread-360427-1-1.html 上篇帖子: Web-Server 中 PHP 的两种工作方式 下篇帖子: PHP与AJAX返回json数据调用
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

扫码加入运维网微信交流群X

扫码加入运维网微信交流群

扫描二维码加入运维网微信交流群,最新一手资源尽在官方微信交流群!快快加入我们吧...

扫描微信二维码查看详情

客服E-mail:kefu@iyunv.com 客服QQ:1061981298


QQ群⑦:运维网交流群⑦ QQ群⑧:运维网交流群⑧ k8s群:运维网kubernetes交流群


提醒:禁止发布任何违反国家法律、法规的言论与图片等内容;本站内容均来自个人观点与网络等信息,非本站认同之观点.


本站大部分资源是网友从网上搜集分享而来,其版权均归原作者及其网站所有,我们尊重他人的合法权益,如有内容侵犯您的合法权益,请及时与我们联系进行核实删除!



合作伙伴: 青云cloud

快速回复 返回顶部 返回列表