当前位置: 移动技术网 > IT编程>开发语言>正则 > PHP和正则表达式教程集合之二第1/2页

PHP和正则表达式教程集合之二第1/2页

2017年12月12日  | 移动技术网IT编程  | 我要评论

  在perl 5.6之前这的确有点困难,不过从perl 5.6之后,引入了递归正则表达式,这个问题得到了解决。通常在正则表达式里用“(?r)”表示一个对自己的引用,下面让我们看看用什么正则表达式来解决刚才提出的问题。
/( ( (?>[^()]+) | (?r) )* )/x 
  现在让我们来分析这个模式的含义,这里使用了“x”模式修正符,以便可以在模式中加入空格以方便阅读。
  模式的开头是匹配第一个左圆括号,然后我们需要捕获的子模式,注意,字模式后面跟了量词“*”,表示此模式可以重复0到多次。最后是一个结束圆括号。现在我们分析子模式( (?>[^()]+) | (?r) )的内容。这是一个分支子模式,表示模式可以有两种情况,第一种是(?>[^()]+),这是一个一次性子模式,代表一个以上的非括号字符,另一种情况是| (?r),也即对正则表达式自己的递归调用——( ( (?>[^()]+) | (?r) )* ),又寻找一个左圆括号,开始查找一对嵌套的圆括号包含的内容。
  分析到这里,这个正则表达式的含义已经基本清楚,但你注意到没有,这里为什么要使用一次性子模式(?>[^()]+)来查找非括号字符串?
  事实上,由于递归的层次是无限的,这种处理非常必要,特别是遇到不匹配的字符串时,它不会让你陷入长时间的等待。考虑一下下面这个目标字符串,
  (aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa()
  在得出不匹配的最终结果前,如果不使用一次性子模式,解析器将尝试每一种可能的方法来分割目标字符串,这将浪费大量的时间。
用正则表达式修改目标
  并非所有的正则表达式工具都允许你修改目标字符串,它们中的一些仅仅使用正则表达式来查找匹配指定模式的字符串,在linux中,最为广泛使用的支持正则表达式的工具就是grep命令,这是一个专门用来查找的工具,再就是一些文本编辑器工具,它们有的允许使用正则表达式替换,有的则不允许,这需要查看你使用的工具的在线手册。
  对于那些允许你使用正则表达式来修改目标字符串的工具中,它们之间的一些不同你必然放在心上:
  这些不同首先表现在替换的具体形式上,有的是以对话框的形式分别让你输入需要查找的模式和被替换的内容,有些则使用命令使界面通过定界符来分隔匹配的模式与需要替换的内容,对于一些编程语言工具,它们通常通过函数的不同参数来分别定义需要匹配的模式与替换的内容。
  另一个需要注意的不同是这些工具具体修改的对象。大多数基于linux的命令行工具一般是通过标准输出或者管道来修改缓存的内容而非直接修改磁盘上存储的文件,而文本编辑器工具或编程语言通常会直接修改目标文件。
  我们下面用linux下sed命令的格式来举几个正则表达式的例子:
  模式:s/cat/dog/g
  输入:wild dogs, bobcats, lions, and other wild cats
  输出:wild dogs, bobdogs, lions, and other wild dogs
  模式:s/[a-z]+i[a-z]*/nice/g
  输入:wild dogs, bobcats, lions, and other wild cats
  输出: nice dogs, bobcats, nice, and other nice cats
  当我们使用模式进行替换操作时,目标字符串中所有匹配模式的字符串都将被替换。
  下面再举一个使用逆向引用进行替换的例子:
  模式:s/([a-z])([0-9]{2,4}) /2:1 /g
  输入: a37 b4 c107 d54112 e1103 xxx
  输出: 37:a b4 107:c d54112 1103:e xxx
  前面已经介绍过默认情况下的匹配一般是greedy的,这常会使实际匹配的部分大于你希望匹配的内容,特别是在进行替换操作时这将更加危险,因为如果你在错误匹配的情况下执行了一次替换操作,实际上你是删除了目标中的有效内容,特别是当这种操作面向文件时造成的危害就更大了。因此,牢记一个不严格的字符类加上一个不严格的量词足以造成不可挽回的后果,执行类似操作前一定要多测试一下不同的目标字符串,尽可能避免这种情况的发生。
  在本教程的下一篇文章里,我们会介绍一款可以方便进行正则表达式学习的工具和一些正则表达式编写的思路。
正则表达式快速入门(五)
在上一篇文章里,我们介绍了正则表达式中的递归与替换,现在让我们接触一个学习正则表达式时方便测试使用的工具,并介绍一些正则表达式的编写思路。
  一个学习正则表达式的便捷工具
  学习正则表达式最好的方法当然是实践,不过支持正则表达式的工具虽多,但如果仅仅用来做练习却不是很方便。
  这里我向一家推荐一款专门的正则表达式编写测试工具,phpedit公司的regular expression editor工具。这是一个免费软件,主要用来调试php使用的perl兼容正则表达式函数。使用它可以方便的输入目标字符串和正则表达式,并实时看到匹配的结果。可以到它的下载网页去下载这个工具。
  程序的界面非常简明,不过使用中发现,它的一些功能使用起来好像有问题,只有preg_match_all和preg_replace功能正常,另外在匹配模式输入框中,不要加模式定界符,程序好像把该输入框中的全部内容都作为模式来解析。
  好在做为一个正则表达式的练习工具,它的功能是足够了,下面是它的运行界面。
程序运行界面
  文中提到的各个例子都可以在里面进行测试,在最上面的框里输入模式,把目标字符串写进中间的输入框,点击“run the regxwp”按钮可以在下面得到匹配结果。
正则表达式的编写思路
  一个避免过多匹配的小技巧
  前面我们已经多此谈到书写不合理的正则表达式引起过多匹配的问题,现在的问题是,如何可以尽量避免类似的情况发生。这里有个小小的技巧。
  如果你发现你定制模式匹配了过多的结果,一个好的方法是换个思路,与其考虑我的模式下一步需要匹配什么,不如考虑我的模式下一步需要避免匹配什么。我们可以用元字答“^”和字符类很容易的达成这种效果,这常常可以得到更精确的匹配。
  为了说明这种思路的好处我们先来举一个与正则表达式无关的例子,考虑这样一个问题,你把一个骰子一次抛出6的概率是六分之一,如果让你掷六次,掷出一个6的概率是多少呢?
  可能有人会这么算,一次的概率是1/6,六次是就是6个1/6,加起来等于1。这个结果明显是错的,虽然你掷了六次,但肯定不能保证必然会掷出一个6。从正向的思路解这道题看上去有点难。
  如果我们换个思路,解决的方法就明确多了。我们可以把这个题的问法改成这样,如果让你掷6次骰子,每一次都掷不出6的概率是多少?这个问题就好解多了,根据概率的乘法原理,每一次掷出不是6的点数的概率是5/6,而6次中每一次都不是6的概率是5/6的6次方,大概等于33%的样子,然后用1减去这个数字就可以得到我们需要的答案。
  你可以把模式中每部分的匹配看作掷一次骰子的过程,每一部分的匹配概率与总匹配概率的情况与我们上面这个例子非常相似。
  如何提高正则表达式的解析效率
  对同样匹配内容的正则表达式而言,一些模式往往比另外一些模式更有效率。举一个简单的例子,使用字符类“[aeiou]”会比使用分支选择型模式“(a|e|i|o|u).”更有效,一般而言,使用尽可能简单和基本的模式通过会得到更高的效率。
  应该尽可能的慎用相互嵌套的无限重复量词,当遇到不匹配的目标字符串时,对字符串的解析有可能花掉很可观的时间。比如下面这个模式片断“(a+)*”,当遇到不匹配的目标字符串“aaaa”时,解析器会对它尝试33种不同的匹配方法,这个数目会随不匹配字符串长度的增加而极快的增长。
  一些正则表达式工具对一些特定的模式匹配进行了优化以提高效率,了解你使用的正则表达式工作做过些什么优化并尽可能利用经过优化的模式可以大大提高你的正则表达式执行效率。例如,php对形如/a+)*b /这样的模式的解析进行了优化,当模式结尾是一个确定的字符时,解析器会先查找目标的结尾是否符合模式,如果否则立刻返回失败的匹配结果并停止解析。如果将上面的样式改为“(a+)*d”时,因为结尾不再是一个确定的字符,此模式会按正常的过程解析。如果你想看一下两者效果的差异,你在我们前面提到的工具中,把目标字符串设置成25个小写的a字符,然后分别测试两个模式,前者立刻就结束了,而后者需要等待约一秒(笔者使用的是xp1700+处理器)。
  除了尽可能利用经过优化的模式,对一些模式进行重新构造也可以大大提高效率。我们在介绍后向断言时介绍过的那个利用后向断言结合一次性子模式匹配结尾的字符的方法就是一个很好的例子。
  这里我们准备结束这个教程,由于篇幅和本人水平的限制文中可能会有很多疏漏,还要请求大家谅解。对正则表达式介绍最全面的可能还是perl相关的一些文档和著作,如果想对正则表达式进行更深入的了解可以参看jeffrey friedl 写的“mastering regular expressions”一书,里面有很多例子。不过我觉得在了解正则表达式基本概念后,还是仔细读一下自己经常使用的相关工具里的正则表达式相关部分更实用一些,最后,还是那句话,实践出真知,希望大家在不断实践中更好的掌握正则表达式的使用。
正则表达式中的特殊字符
转自:http://www.phpe.net/articles/151.shtml 
字符 描述 
将下一个字符标记为一个特殊字符、或一个原义字符、或一个后向引用、或一个八进制转义符。例如,'n' 匹配字符 "n"。'n' 匹配一个换行符。序列 '' 匹配 "" 而 "(" 则匹配 "("。
^
匹配输入字符串的开始位置。如果设置了 regexp 对象的 multiline 属性,^ 也匹配 'n' 或 'r' 之后的位置。
$
匹配输入字符串的结束位置。如果设置了regexp 对象的 multiline 属性,$ 也匹配 'n' 或 'r' 之前的位置。
*
匹配前面的子表达式零次或多次。例如,zo* 能匹配 "z" 以及 "zoo"。 * 等价于{0,}。
+ 匹配前面的子表达式一次或多次。例如,'zo+' 能匹配 "zo" 以及 "zoo",但不能匹配 "z"。+ 等价于 {1,}。
?
匹配前面的子表达式零次或一次。例如,"do(es)?" 可以匹配 "do" 或 "does" 中的"do" 。? 等价于 {0,1}。
{n}
n 是一个非负整数。匹配确定的 n 次。例如,'o{2}' 不能匹配 "bob" 中的 'o',但是能匹配 "food" 中的两个 o。
{n,}
n 是一个非负整数。至少匹配n 次。例如,'o{2,}' 不能匹配 "bob" 中的 'o',但能匹配 "foooood" 中的所有 o。'o{1,}' 等价于 'o+'。'o{0,}' 则等价于 'o*'。
{n,m}
m 和 n 均为非负整数,其中n <= m。最少匹配 n 次且最多匹配 m 次。刘, "o{1,3}" 将匹配 "fooooood" 中的前三个 o。'o{0,1}' 等价于 'o?'。请注意在逗号和两个数之间不能有空格。
?
当该字符紧跟在任何一个其他限制符 (*, +, ?, {n}, {n,}, {n,m}) 后面时,匹配模式是非贪婪的。非贪婪模式尽可能少的匹配所搜索的字符串,而默认的贪婪模式则尽可能多的匹配所搜索的字符串。例如,对于字符串 "oooo",'o+?' 将匹配单个 "o",而 'o+' 将匹配所有 'o'。

匹配除 "n" 之外的任何单个字符。要匹配包括 'n' 在内的任何字符,请使用象 '[.n]' 的模式。
(pattern)
匹配pattern 并获取这一匹配。所获取的匹配可以从产生的 matches 集合得到,在vbscript 中使用 submatches 集合,在jscript 中则使用 {content}… 属性。要匹配圆括号字符,请使用 '(' 或 ')'。
(?:pattern)
匹配 pattern 但不获取匹配结果,也就是说这是一个非获取匹配,不进行存储供以后使用。这在使用 "或" 字符 (|) 来组合一个模式的各个部分是很有用。例如, 'industr(?:y|ies) 就是一个比 'industry|industries' 更简略的表达式。
(?=pattern)
正向预查,在任何匹配 pattern 的字符串开始处匹配查找字符串。这是一个非获取匹配,也就是说,该匹配不需要获取供以后使用。例如, 'windows (?=95|98|nt|2000)' 能匹配 "windows 2000" 中的 "windows" ,但不能匹配 "windows 3.1" 中的 "windows"。预查不消耗字符,也就是说,在一个匹配发生后,在最后一次匹配之后立即开始下一次匹配的搜索,而不是从包含预查的字符之后开始。
(?!pattern)
负向预查,在任何不匹配negative lookahead matches the search string at any point where a string not matching pattern 的字符串开始处匹配查找字符串。这是一个非获取匹配,也就是说,该匹配不需要获取供以后使用。例如'windows (?!95|98|nt|2000)' 能匹配 "windows 3.1" 中的 "windows",但不能匹配 "windows 2000" 中的 "windows"。预查不消耗字符,也就是说,在一个匹配发生后,在最后一次匹配之后立即开始下一次匹配的搜索,而不是从包含预查的字符之后开始
x|y 
匹配 x 或 y。例如,'z|food' 能匹配 "z" 或 "food"。'(z|f)ood' 则匹配 "zood" 或 "food"。
[xyz]
字符集合。匹配所包含的任意一个字符。例如, '[abc]' 可以匹配 "plain" 中的 'a'。
[^xyz]
负值字符集合。匹配未包含的任意字符。例如, '[^abc]' 可以匹配 "plain" 中的'p'。
[a-z]
字符范围。匹配指定范围内的任意字符。例如,'[a-z]' 可以匹配 'a' 到 'z' 范围内的任意小写字母字符。
[^a-z]
负值字符范围。匹配任何不在指定范围内的任意字符。例如,'[^a-z]' 可以匹配任何不在 'a' 到 'z' 范围内的任意字符。

匹配一个单词边界,也就是指单词和空格间的位置。例如, 'erb' 可以匹配"never" 中的 'er',但不能匹配 "verb" 中的 'er'。

匹配非单词边界。'erb' 能匹配 "verb" 中的 'er',但不能匹配 "never" 中的 'er'。
cx
匹配由x指明的控制字符。例如, cm 匹配一个 control-m 或回车符。 x 的值必须为 a-z 或 a-z 之一。否则,将 c 视为一个原义的 'c' 字符。
d
匹配一个数字字符。等价于 [0-9]。
d
匹配一个非数字字符。等价于 [^0-9]。
f
匹配一个换页符。等价于 x0c 和 cl。

匹配一个换行符。等价于 x0a 和 cj。
r
匹配一个回车符。等价于 x0d 和 cm。
匹配任何空白字符,包括空格、制表符、换页符等等。等价于 [ fnrtv]。
匹配任何非空白字符。等价于 [^ fnrtv]。
t
匹配一个制表符。等价于 x09 和 ci。
v
匹配一个垂直制表符。等价于 x0b 和 ck。
w
匹配包括下划线的任何单词字符。等价于'[a-za-z0-9_]'。
w
匹配任何非单词字符。等价于 '[^a-za-z0-9_]'。
xn
匹配 n,其中 n 为十六进制转义值。十六进制转义值必须为确定的两个数字长。例如, 'x41' 匹配 "a"。'x041' 则等价于 'x04' & "1"。正则表达式中可以使用 ascii 编码。.
num
匹配 num,其中 num 是一个正整数。对所获取的匹配的引用。例如,'(.)' 匹配两个连续的相同字符。

标识一个八进制转义值或一个后向引用。如果 n 之前至少 n 个获取的子表达式,则 n 为后向引用。否则,如果 n 为八进制数字 (0-7),则 n 为一个八进制转义值。
nm
标识一个八进制转义值或一个后向引用。如果 nm 之前至少有is preceded by at least nm 个获取得子表达式,则 nm 为后向引用。如果 nm 之前至少有 n 个获取,则 n 为一个后跟文字 m 的后向引用。如果前面的条件都不满足,若  n 和 m 均为八进制数字 (0-7),则 nm 将匹配八进制转义值 nm。
nml
如果 n 为八进制数字 (0-3),且 m 和 l 均为八进制数字 (0-7),则匹配八进制转义值 nml。
un
匹配 n,其中 n 是一个用四个十六进制数字表示的 unicode 字符。例如,u00a9 匹配版权符号 (?)。 
2

如对本文有疑问, 点击进行留言回复!!

相关文章:

验证码:
移动技术网