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

正则表达式难于书写、难于阅读、难于维护,经常错误匹配意料不到的文本或者错过了有效的文本,这些问题都是由正则表达式的表现和能力引起的。每个元字符(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和代码的维护,如果你自己就是这个代码的维护者你将倍感轻松。 

时间: 2024-09-13 00:52:28

[php]正则表达式的五个成功习惯_正则表达式的相关文章

正则表达式详细介绍(上)_正则表达式

本文是Jan Goyvaerts为RegexBuddy写的教程的译文,下面来看吧! 1. 什么是正则表达式 基本说来,正则表达式是一种用来描述一定数量文本的模式.Regex代表Regular Express.本文将用<<regex>>来表示一段具体的正则表达式. 一段文本就是最基本的模式,简单的匹配相同的文本. 2. 不同的正则表达式引擎 正则表达式引擎是一种可以处理正则表达式的软件.通常,引擎是更大的应用程序的一部分.在软件世界,不同的正则表达式并不互相兼容.本教程会集中讨论Pe

正则表达式详细介绍(下)_正则表达式

本文是前一片文章<正则表达式详细介绍(上)>的续篇,在本文中讲述了正则表达式中的组与向后引用,先前向后查看,条件测试,单词边界,选择符等表达式及例子,并分析了正则引擎在执行匹配时的内部机理. 9. 单词边界 元字符<<\b>>也是一种对位置进行匹配的"锚".这种匹配是0长度匹配. 有4种位置被认为是"单词边界": 1) 在字符串的第一个字符前的位置(如果字符串的第一个字符是一个"单词字符") 2) 在字符串的最

javascript正则表达式分析第1/2页_正则表达式

什么是正则表达式? 这个问题可以参见:"正则表达式30分钟入门教程",很多编程语言都支持正则表达式,本文仅仅讨论JavaScript中的正则表达式. 创建一个正则表达式 第一种方法: 复制代码 代码如下: var reg = /pattern/; 第二种方法: 复制代码 代码如下: var reg = new RegExp('pattern'); 正则表达式的exec方法简介 语法: reg.exec(str); 其中str为要执行正则表达式的目标字符串. 例如: 复制代码 代码如下:

正则表达式中\w不能识别中文_正则表达式

正则表达式用于字符串处理,表单验证等场合,实用高效,但用到时总是不太把握,以致往往要上网查一番.我将一些常用的表达式收藏在这里,作备忘之用.本贴随时会更新. 匹配中文字符的正则表达式: [\u4e00-\u9fa5] 匹配双字节字符(包括汉字在内):[^\x00-\xff] 应用:计算字符串的长度(一个双字节字符长度计2,ASCII字符计1) String.prototype.len=function(){return this.replace([^\x00-\xff]/g,"aa")

正则表达式零宽断言详解_正则表达式

正则表达式零宽断言: 零宽断言是正则表达式中的难点,所以本章节重点从匹配原理方面进行一下分析.零宽断言还有其他的名称,例如"环视"或者"预搜索"等等,不过这些都不是我们关注的重点. 一.基本概念: 零宽断言正如它的名字一样,是一种零宽度的匹配,它匹配到的内容不会保存到匹配结果中去,最终匹配结果只是一个位置而已. 作用是给指定位置添加一个限定条件,用来规定此位置之前或者之后的字符必须满足限定条件才能使正则中的字表达式匹配成功. 注意:这里所说的子表达式并非只有用小括号

linux grep正则表达式与grep用法详解_正则表达式

需要大家牢记:正则表达式与通配符不一样,它们表示的含义并不相同      正则表达式只是字符串的一种描述,只有和支持正则表达式的工具相结合才能进行字符串处理.本文以grep为例来讲解正则表达式. grep命令 功能:输入文件的每一行中查找字符串. 基本用法: grep [-acinv] [--color=auto] [-A n] [-B n] '搜寻字符串' 文件名 参数说明: -a:将二进制文档以文本方式处理 -c:显示匹配次数 -i:忽略大小写差异 -n:在行首显示行号 -A:After的意

WEB开发时常用的正则表达式(PHP和Javascript)_正则表达式

在WEB开发中,正则表达式通常用来检测.查找替换某些符合规则的字符串,如检测用户输入E-mai格式是否正确,采集符合规则的页面内容等等. 下面分别用PHP和Javscript向大家介绍WEB开发中最常用最实用的正则表达式及其用法. PHP常用表达式用法1.匹配正整数:/^[1-9]\d*$/ 2.匹配非负整数(正整数+0):/^\d+$/ 3.匹配中文:/^[\x{4e00}-\x{9fa5}]+$/u 4.匹配Email:/^\w+([-+.]\w+)*@\w+([-.]\w+)*\.\w+(

JavaScript正则表达式上之基本语法(推荐)_正则表达式

相关阅读: js正则表达式基本语法(精粹) 正则表达式语法 一个正则表达式就是由普通字符(例如字符 a 到 z)以及特殊字符(称为元字符)组成的文字模式.该模式描述在查找文字主体时待匹配的一个或多个字符串.正则表达式作为一个模板,将某个字符模式与所搜索的字符串进行匹配. 定义 JavaScript种正则表达式有两种定义方式,定义一个匹配类似 <%XXX%> 的字符串 1. 构造函数 复制代码 代码如下: var reg=new RegExp('<%[^%>]+%>','g')

JS中的正则表达式及pattern的注意事项_正则表达式

RegExp对象的创建: 常规的正则表达式的创建可用直接量,即斜杠 "/" 括起来的字符.但在要求参数变化的环境下,RegExp()构造函数是更好的选择: var reg1 = /'\w+'/g; var reg2 = new RegExp('\'\\w+\'','g'); 对比两种创建方式,RegExp中的第一个参数为要创建的正则字符串,一方面注意,因为不是直接量的表示形式,因此不用斜杠" / "括起来了:而是字符串中必须要对引号" ' "和转