[php]正则表达式的五个成功习惯

2014-08-20  来源:本站原创  分类:正则表达式  人气:0 

正则表达式难于书写、难于阅读、难于维护,经常错误匹配意料不到的文本或者错过了有效的文本,这些问题都是由正则表达式的表现和能力引起的。每个元字符(metacharacter)的能力和细微差别组合在一起,使得代码不借助于智力技巧就无法解释。
许多包含一定特性的工具使阅读和编写正则表达式变得容易了,但是它们又很不符合习惯。对于很多程序员来说,书写正则表达式就是一种魔法艺术。他们坚持自己所知道的特征并持有绝对乐观的态度。如果你愿意采用本文所探讨的五个习惯,你将可以让你设计的正则表达式经受的住反复试验。
本文将使用Perl、PHP和Python语言作为代码示例,但是本文的建议几乎适用于任何替换表达式(regex)的执行。

一、使用空格和注释
对于大部分程序员来说,在一个正则表达式环境里使用空格和缩进排列都不成问题,如果他们没有这么做一定会被同行甚至外行人士看笑话。几乎每个人都知道把代码挤在一行会难于阅读、书写和维护。对于正则表达式又有什么不同呢?
大部分替换表达式工具都具有扩展的空格特性,这允许程序员把他们的正则表达式扩展为多行,并在每一行结尾加上注释。为什么只有少部分程序员利用这个特性呢?Perl 6的正则表达式默认就是扩展空格的模式。不要再让语言替你默认扩展空格了,自己主动利用吧。
记住扩展空格的窍门之一就是让正则表达式引擎忽略扩展空格。这样如果你需要匹配空格,你就不得不明确说明。
在Perl语言里面,在正则表达式的结尾加上x,这样“m/foo|bar/”变为如下形式:
m/
foo

bar
/x
在PHP语言里面,在正则表达式的结尾加上x,这样“"/foo|bar/"”变为如下形式:
"/
foo

bar
/x"
在Python语言里面,传递模式修饰参数“re.VERBOSE”得到编译函数如下:
pattern = r'''
foo

bar
'''
regex = re.compile(pattern, re.VERBOSE)
处理更加复杂的正则表达式时,空格和注释就更能体现出其重要性。假设下面的正则表达式用于匹配美国的电话号码:
\(?\d{3}\)? ?\d{3}[-.]\d{4}
这个正则表达式匹配电话号码如“(314)555-4000”的形式,你认为这个正则表达式是否匹配“314-555-4000”或者“555- 4000”呢?答案是两种都不匹配。写上这么一行代码隐蔽了缺点和设计结果本身,电话区号是需要的,但是正则表达式在区号和前缀之间缺少一个分隔符号的说明。
把这一行代码分成几行并加上注释将把缺点暴露无疑,修改起来显然更容易一些。
在Perl语言里面应该是如下形式:
/
\(? # 可选圆括号
\d{3} # 必须的电话区号
\)? # 可选圆括号
[-\s.]? # 分隔符号可以是破折号、空格或者句点
\d{3} # 三位数前缀
[-.] # 另一个分隔符号
\d{4} # 四位数电话号码
/x
改写过的正则表达式现在在电话区号后有一个可选择的分隔符号,这样它应该是匹配“314-555-4000”的,然而电话区号还是必须的。另一个程序员如果需要把电话区号变为可选项则可以迅速看出它现在不是可选的,一个小小的改动就可以解决这个问题。

二、书写测试
一共有三个层次的测试,每一层为你的代码加上一层可靠性。首先,你需要认真想想你需要匹配什么代码以及你是否能够处理错误匹配。其次,你需要利用数据实例来测试正则表达式。最后,你需要正式通过一个测试小组的测试。
决定匹配什么其实就是在匹配错误结果和错过正确结果之间寻求一个平衡点。如果你的正则表达式过于严格,它将会错过一些正确匹配;如果它过于宽松,它将会产生一个错误匹配。一旦某个正则表达式发放到实际代码当中,你可能不会两者都注意到。考虑一下上面电话号码的例子,它将会匹配“800-555-4000 = -5355”。错误的匹配其实很难发现,所以提前规划做好测试是很重要的。
还是使用电话号码的例子,如果你在Web表单里面确认一个电话号码,你可能只要满足于任何格式的十位数字。但是,如果你想从大量文本里面分离电话号码,你可能需要很认证的排除不符合要求的错误匹配。
在考虑你想匹配的数据的时候,写下一些案例情况。针对案例情况写下一些代码来测试你的正则表达式。任何复杂的正则表达式都最好写个小程序测试一下,可以采用下面的具体形式。
在Perl语言里面:
#!/usr/bin/perl

my @tests = ( "314-555-4000",
"800-555-4400",
"(314)555-4000",
"314.555.4000",
"555-4000",
"aasdklfjklas",
"1234-123-12345"
);

foreach my $test (@tests) {
if ( $test =~ m/
\(? # 可选圆括号
\d{3} # 必须的电话区号
\)? # 可选圆括号
[-\s.]? # 分隔符号可以是破折号、空格或者句点
\d{3} # 三位数前缀
[-\s.] # 另一个分隔符号
\d{4} # 四位数电话号码
/x ) {
print "Matched on $test\n";
}
else {
print "Failed match on $test\n";
}
}

在PHP语言里面:
<?php
$tests = array( "314-555-4000",
"800-555-4400",
"(314)555-4000",
"314.555.4000",
"555-4000",
"aasdklfjklas",
"1234-123-12345"
);

$regex = "/
\(? # 可选圆括号
\d{3} # 必须的电话区号
\)? # 可选圆括号
[-\s.]? # 分隔符号可以是破折号、空格或者句点
\d{3} # 三位数前缀
[-\s.] # 另一个分隔符号
\d{4} # 四位数电话号码
/x";

foreach ($tests as $test) {
if (preg_match($regex, $test)) {
echo "Matched on $test<br />;";
}
else {
echo "Failed match on $test<br />;";
}
}
?>;

在Python语言里面:
import re

tests = ["314-555-4000",
"800-555-4400",
"(314)555-4000",
"314.555.4000",
"555-4000",
"aasdklfjklas",
"1234-123-12345"
]

pattern = r'''
\(? # 可选圆括号
\d{3} # 必须的电话区号
\)? # 可选圆括号
[-\s.]? # 分隔符号可以是破折号、空格或者句点
\d{3} # 三位数前缀
[-\s.] # 另一个分隔符号
\d{4} # 四位数电话号码
'''

regex = re.compile( pattern, re.VERBOSE )

for test in tests:
if regex.match(test):
print "Matched on", test, "\n"
else:
print "Failed match on", test, "\n"

运行测试代码将会发现另一个问题:它匹配“1234-123-12345”。
理论上,你需要整合整个程序所有的测试到一个测试小组里面。即使你现在还没有测试小组,你的正则表达式测试也会是一个小组的良好基础,现在正是开始创建的好机会。即使现在还不是创建的合适时间,你也应该在每次修改以后运行测试一下正则表达式。这里花费一小段时间将会减少你很多麻烦事。

三、为交替操作分组
交替操作符号(|)的优先级很低,这意味着它经常交替超过程序员所设计的那样。比如,从文本里面抽取Email地址的正则表达式可能如下:
^CC:|To:(.*)
上面的尝试是不正确的,但是这个bug往往不被注意。上面代码的意图是找到“CC:”或者“To:”开始的文本,然后在这一行的后面部分提取Email地址。
不幸的是,如果某一行中间出现“To:”,那么这个正则表达式将捕获不到任何以“CC:”开始的一行,而是抽取几个随机的文本。坦白的说,正则表达式匹配 “CC:”开始的一行,但是什么都捕获不到;或者匹配任何包含“To:”的一行,但是把这行的剩余文本都捕获了。通常情况下,这个正则表达式会捕获大量 Email地址,所有没有人会注意这个bug。
如果要符合实际意图,那么你应该加入括号说明清楚,正则表达式如下:
(^CC:)|(To:(.*))
如果真正意图是捕获以“CC:”或者“To:”开始的文本行的剩余部分,那么正确的正则表达式如下:
^(CC:|To:)(.*)
这是一个普遍的不完全匹配的bug,如果你养成为交替操作分组的习惯,你就会避免这个错误。

四、使用宽松数量词
很多程序员避免使用宽松数量词比如“*?”、“+?”和“??”,即使它们会使这个表达式易于书写和理解。
宽松数量词可以尽可能少的匹配文本,这样有助于完全匹配的成功。如果你写了“foo(.*?)bar”,那么数量词将在第一次遇到“bar”时就停止匹配,而不是在最后一次。如果你希望从“foo###bar+++bar”中捕获“###”,这一点就很重要。一个严格数量词将捕获“###bar++ +”。
假设你要从HTML文件里面捕获所有电话号码,你可能会使用我们上文讨论过的电话号码正则表达式的例子。但是,如果你知道所有电话号码都在一个表格的第一列里面,你可以使用宽松数量词写出更简单的正则表达式:
<tr>;<td>;(.+?)<td>;
很多刚起步的程序员不使用宽松数量词来否定特定种类。他们能写出下面的代码:
<tr>;<td>;([^>;]+)</td>;
这种情况下它可以正常运行,但是如果你想捕获的文本包含有你分隔的公共字符(这种情况下比如</td>;),这将会带来很大麻烦。如果你使用了宽松数量词,你只要花上很少的时间组装字符种类就能产生新的正则表达式。
在你知道你要捕获文本的环境结构时,宽松数量词是具有很大价值的。

五、利用可用分界符
Perl 和PHP语言常常使用左斜线(/)来标志一个正则表达式的开头和结尾,Python语言使用一组引号来标志开头和结尾。如果在Perl和PHP中坚持使用左斜线,你将要避免表达式中的任何斜线;如果在Python中使用引号,你将要避免使用反斜线(\)。选择不同的分界符或引号可以允许你避免一半的正则表达式。这将使得表达式易于阅读,减少由于忘记避免符号而潜在的bug。
Perl和PHP语言允许使用任何非数字和空格字符作为分界符。如果你切换到一个新的分界符,在匹配URL或HTML标志(如“http://”或“<br/>;”)时,你就可以避免漏掉左斜线了。
例如,“/http:\/\/(\S)*/”可以写为“#http://(\S)*#”。
通用分界符是“#”、“!”和“|”。如果你要使用方括号、尖括号或者花括号,只要保持前后配对出现就可以了。下面就是一些通用分界符的示例:
#…# !…! {…} s|…|…| (Perl only) s[…][…] (Perl only) s<…>;/…/ (Perl only)
在Python中,正则表达式首先会被当作一个字符串。如果你使用引号作为分界符,你将漏掉所有反斜线。但是你可以使用“r''”字符串避免这个问题。如果针对“re.VERBOSE”选项使用三个连续单引号,它将允许你包含换行。例如 regex = "(\\w+)(\\d+)"可以写出下面的形式:
regex = r'''
(\w+)
(\d+)
'''

小结:本文的建议主要着眼于正则表达式的可读性,在开发中养成这些习惯,你将会更加清晰的考虑设计和表达式的结构,这将有助于减少bug和代码的维护,如果你自己就是这个代码的维护者你将倍感轻松。

相关文章
  • [php]正则表达式的五个成功习惯 2014-08-20

    正则表达式难于书写.难于阅读.难于维护,经常错误匹配意料不到的文本或者错过了有效的文本,这些问题都是由正则表达式的表现和能力引起的.每个元字符(metacharacter)的能力和细微差别组合在一起,使得代码不借助于智力技巧就无法解释. 许多包含一定特性的工具使阅读和编写正则表达式变得容易了,但是它们又很不符合习惯.对于很多程序员来说,书写正则表达式就是一种魔法艺术.他们坚持自己所知道的特征并持有绝对乐观的态度.如果你愿意采用本文所探讨的五个习惯,你将可以让你设计的正则表达式经受的住反复试验.

  • 浪潮之巅第十五章 成功的转基因(二):道琼斯的常青树(3M) 2014-10-13

    第一节 从木工厂到手机之王(诺基亚 Nokia) 第二节 道琼斯的常青树(3M) 世界上影响力最大的股票指数道琼斯,只覆盖了 30 个公司.但这三十个公司是美国经济中最具代表性.长期发展稳定并且关乎到美国经济命脉的大公司.道琼斯不断地淘汰过时的公司然后吸收新鲜血液,思科.英特尔和微软就是近年来被加入进去的.而许多曾经辉煌的公司比如美国钢铁和合并前的 AT&T 就被淘汰出去了.由于美国和世界产业的变迁,能几十年一直在道琼斯指数中的公司寥寥无几,3M 公司便是这凤毛麟角中的一个.到 2007 年,3

  • 浪潮之巅第十五章 成功的转基因(四)世界最大的联合体(GE)3.2 从实体经济到金融 2014-03-12

    第一节 从木工厂到手机之王(诺基亚 Nokia) 第二节 道琼斯的常青树(3M) 第三节 世界最大的联合体(GE) 3.1 百年扩张,从有线电到无线电 3.2 从实体经济到金融 GE 最近的扩张是进入银行和金融领域.在金融风暴前,银行和金融部门对 GE 的重要性甚至超过其它部门.银行和金融业务看上去和 GE 原有的实体工业无关,GE 此前在金融业也没有什么经验,这样的扩展一般来讲是非常忌讳的.但是,GE 这种非常规的扩张在它特殊情况下却是合理而有根据的. GE 的家电产品大多数是同类产品中高端的

  • 浪潮之巅第十五章 成功的转基因(三)世界最大的联合体(GE)3.1 百年扩张,从有线电 2014-06-20

    第一节 从木工厂到手机之王(诺基亚 Nokia) 第二节 道琼斯的常青树(3M) 第三节 世界最大的联合体(GE) 了解 GE 公司历史的人都知道这个充满传奇色彩的公司是由著名发明家爱迪生创立的,是将电最早介绍和普及到世界上的公司.它现在的英文名字是 General Electronic,GE 是它的简称,一百年前它进入中国时,当时根据字面意思和它经营的产品,将 GE 翻译成通用电气公司.也许是因为爱迪生发明电灯的故事家喻户晓了,今天 GE 在大部分人印象中仍然是生产电灯.电冰箱等家电的公司.但

  • 浪潮之巅第十五章 成功的转基因(五)世界最大的联合体(GE)3.3 领袖的重要性 2015-03-11

    第一节 从木工厂到手机之王(诺基亚 Nokia) 第二节 道琼斯的常青树(3M) 第三节 世界最大的联合体(GE) 3.1 百年扩张,从有线电到无线电 3.2 从实体经济到金融 3.3 领袖的重要性 即使不断在淘汰旧的.过时的部门,GE的部门相比任何一个公司还是太多了,把这样一个联合体整合好是一件非常非常难的事.郭士纳把庞大的 IBM 帝国比喻成大象.而 GE 的业务种类比 IBM 又多了很多,我们把它比成恐龙恐怕也不过分.我们在前面的章节中讲到了,很多公司当扩张到一定规模,管理就开始失控,最后

  • 浪潮之巅第十五章 成功的转基因(一):从木工厂到手机之王(诺基亚 Nokia) 2014-02-20

    我们在介绍基因决定定律的时候讲,一个公司的基因常常决定了它的命运.而转基因对于大多数公司来讲是近乎不可能的.但是凡事总有例外,总有一些了不起的公司成功地从一个衰退的行业转到新的高速发展的行业.这样,公司之间就有了伟大和平庸的差别.我们在这一章里将介绍三个不同的成功的例子,分别代表了三种不同的转基因的成功方法. 第一节 从木工厂到手机之王(诺基亚 Nokia) 诺基亚是"浪潮之巅"系列中介绍的唯一一家欧洲公司.我很少介绍欧洲公司倒不是因为它们不好或者不重要,只是他们在近百年科技发展中不具

  • python正则表达式match和search用法实例 2014-05-04

    这篇文章主要介绍了python正则表达式match和search用法,实例分析了正则表达式中match和search的功能.定义及相关使用技巧,具有一定参考借鉴价值,需要的朋友可以参考下 本文实例讲述了python正则表达式match和search用法.分享给大家供大家参考.具体分析如下: python提供了2中主要的正则表达式操作:re.match 和 re.search. match :只从字符串的开始与正则表达式匹配,匹配成功返回matchobject,否则返回none: search :

  • 如何使用JavaScript和正则表达式进行数据验证 2015-04-01

    利用客户端JavaScript的优势,JavaScript中的正则表达式可以简化数据验证的工作,下面与大家分享下如何使用JavaScript和正则表达式进行数据验证,感兴趣的朋友可以参考下哈 数据验证是网络应用软件从客户端接受数据的重要步骤,毕竟,您需要在使用客户数据前确保其符合预期的格式.在网络应用程序中,您可以选择使用特定平台的工具,比如ASP.NET.JSP等等,或者您可以利用客户端JavaScript的优势,JavaScript中的正则表达式可以简化数据验证的工作. 正则表达式 正则表达

  • 成功企业在创业初期的"冠军相" 2013-12-18

    当前绝大多数管理理论和书籍都是关于成熟企业和大型企业的,实际上即使是今天的世界500强企业在他们的创业阶段并不见得天生就是"龙钟",并不见得与其他创业型企业有什么特别的不同,换句话说,他们的成功原因绝对不是今天可以看到的支撑他们辉煌的那些东西,也可以说他们在创业阶段的内外部条件和管理方式肯定是与今天不一样的,那么到底是什么东西导致他们从当初混迹于"芸芸众生"之中,到后来一步一步走向辉煌的呢? 成功的创业企业并不象我们通常所认为的那样具有"新奇的创意&quo

  • ruby 标准类型总结 2014-05-19

    诠释分析了ruby的标准类型,学习ruby的朋友,需要了解和掌握的. 一.数字 Ruby支持整数和浮点数,整数可以是任意长度 一定范围内的整数以二进制存放,它们属于fixnum类型,当超出这个范围时则自动转换为bignum类型 表达方式:符号+一串字符,数字串中的下划线会被忽略,(前缀包括:0表示八进制, 0x表示十六进制, 0b表示二进制)123_456_789_123_345_789 # Bignum 0xaabb # 十六进制 也可以通过在前面加上问号来得到ASCII码字符对应的整数值和转

  • 记我配置Nginx代理的遭遇 2014-11-08

    我一直觉得自己的Nginx知识还算过得去,可是我错了,配置Nginx代理的遭遇让我苦不堪言,即便如此,我还是挣扎着记录一二,以便让后来者能够踩着我的足迹继续前进. 说起来非常简单:某项目的搜索功能升级了,需要把请求从旧的服务代理到新的服务上面去,其中有点儿不一样的地方是参数的传递形式发生的变化,例子如下: 旧:http://www.old.com/search/lamp 新:http://www.new.com/search?q=lamp 第一次尝试: location ~ ^/search/(

  • 我们为什么来到这个世界上? 2013-11-16

    几月前,在清华大学初次见到正在演讲的黄金雄教授,虽然通过吴莹莹的介绍,<程序员>早在数年前就已和黄教授有过联系,但这却是第一次双方面对面的深入沟通.话题较为宽泛,从架构师的实践到当前的软件培训和教育,希望以后有机会能让读者阅读到更多黄教授的专业文章. 知道他最近出版了一本书<为什么时光不能倒流>,拿到书时让我吃了一惊.我的想像中她应该是一本充满数学.计算机.哲学味道的哲理书,却没想到,竟是一本讲述儿时故事.满溢家庭温情的心灵读本. 下面摘取部分篇章,与大家分享: 我们为什么来到这个

  • 微软首席软件架构师雷·奥兹:盖茨的另一个接班人 2014-02-10

    再见,比尔!这个让我们又爱又恨的人让个人电脑成为大众商品,其继任者雷·奥兹能否抓住科技业的下一场革命-云计算-并让微软重焕青春? 文 <环球企业家> 黄继新 此刻,关于微软,你还能想起什么--除了收购雅虎未遂? 在见到52岁的雷·奥兹(Ray Ozzie)之前,我们也试图问自己这个问题.这个从下月起就将正式接任比尔·盖茨 "首席软件架构师"头衔的人,会如何看待今天的微软? 奥兹著名的前任马上要完成他漫长的告别.那个商业界.科技界.慈善界的奇才,从两年前的6月份起就宣布了退休

  • Windows 7六种武器终极PK WinXP 2014-06-20

    6月3日,微软OEM事业部全球副总裁史蒂夫·古杰海默Steve Guggenheimer 在台北国际计算机展(Computex)上正式宣布,新一代操作系统Windows 7将于2009年10月22日在全球同步上市.也就是说这个肩负着挽救Vista低迷状态的继任者,即在两个月后正式与我们见面,而它的对手恰恰是已有8岁"高龄"的WinXP. 最近几年,市场好像和我们开了一个大玩笑,原本早该退位的WinXP一再延迟,本来准备接替XP的Vista却一败涂地.以至于霸气十足的微软公司也不得不低调

  • 访开心网总裁程炳皓:我想打开你的心 2014-09-02

    2008年的最后一天,上百人在开心网上给程炳皓送礼并且提醒他:"一个老男人的青春小鸟飞得更远了,你--更成熟了."他一边感慨一边在"记录"里回应着:"谢谢啊!" 在这个开心网创始人的眼中,类似的互动几乎就是网站核心价值的具象:让熟识的朋友在虚拟世界里开上一个小玩笑,得到一丝小快乐.2008年末,开心网总裁程炳皓首度接受媒体的独家专访,对许多悬置已久的问题进行正面回答 12月30日,北京的气温降到了零下.北大的学生们在未名湖上肆意地溜着冰,中关村的

  • php小经验:解析preg_match与preg_match_all 函数 2014-04-16

    本篇文章是对php中的preg_match函数与preg_match_all函数进行了详细的分析介绍,需要的朋友参考下 正则表达式在 PHP 中的应用在 PHP 应用中,正则表达式主要用于: •正则匹配:根据正则表达式匹配相应的内容 •正则替换:根据正则表达式匹配内容并替换 •正则分割:根据正则表达式分割字符串 在 PHP 中有两类正则表达式函数,一类是 Perl 兼容正则表达式函数,一类是 POSIX 扩展正则表达式函数.二者差别不大,而且推荐使用Perl 兼容正则表达式函数,因此下文都是以

  • 创业总结:创业公司怎样留人 2014-09-18

    创业阶段的公司,人是第一位的,事儿是人做的.资源在人手里,团队决定了项目的路能走多远,投资人最看重的也是团队.创业公司没钱没名气,招人.留人都很难,团队人员流失,不仅耽误项目进度,也影响士气,招人和留人是创业团队最头疼的问题之一,根据我创业时验证的结果总结已下几点经验: 1.价值观第一位.招人时就不要用钱来吸引人,为钱而来的也容易为钱而走,创业有风险有低谷,短期不盈利很正常,但是为钱而来的遇到困难更可能退缩.我当时技术团队给的是低于业内的水平的保障生活的工资,4-5个全职合作伙伴和20多个兼职运

  • 蛟龙号创深潜纪录可征服全球99.8%海底 2013-11-07

    "蛟龙"完成第四次下潜回归母船"向阳红09"船(科技日报记者陈瑜摄/新浪科技配图) 迎接三位潜航员的是鲜花.香槟 (科技日报记者陈瑜摄/新浪科技配图) 本报讯(记者邓杭)昨天,中国"蛟龙"号载人潜水器在西太平洋的马里亚纳海沟试验海区创造了中国载人深潜最新纪录,首次突破7000米,达到7020米.这是世界同类型载人潜水器的最大下潜深度.7000米深度也是"蛟龙"号的最大设计深度. 中国载人潜水器"蛟龙"号于北

  • 海南文昌发射场可能承载长征5号火箭发射 2013-12-04

    地处北纬19.9度离赤道近完全对外开放 可"月月发射"中国人登月或从海南文昌出发-- 文昌发射场将"抢"国际生意 明年,海南文昌发射场将建成,由于地处北纬19.9度,它将成为我国离赤道最近的发射场,我国航天发射综合能力将得到质的提升. 日前,全国人大代表.中国航天科技集团航天推进技术研究院院长谭永华在接受记者采访时说,现在我国的运载火箭的近地轨道运载能力最大只有9吨,"长征5号"的近地轨道运载能力可以提高到25吨. 由于文昌发射场是完全对外开放的

  • 浪潮预计服务器业务09年销售额增长25% 2014-03-30

    新浪科技讯 5月27日消息,相关人士近日透露,浪潮服务器业务09财年的业绩目标和08财年持平,为销量增长15%,销售额增长25%,这一增速为行业平均速度的两倍.为此,浪潮服务器部门近日发布了名为"金鼎计划"的2009财年营销策略,将重点锁定文化.卫生等行业市场. 浪潮集团服务器存储营销本部市场推进部总经理庞松涛近日介绍说,"金鼎"寓意通过"细分.聚焦.协同"这三大策略,缩小与国际品牌在应用层面的差距.具体而言,"细分"是指细分