正则表达式详解

一.介绍

正则表达式是一种用于描述字符排列和匹配模式的一种语法规则。它主要用于字符串的模式分割、匹配、查找及替换操作。正则表达式的应用非常广,已经超出了语言的局限,大部分编程语言都提供了对正则表达式的支持。包括*nix(Linux, Unix等),HP等操作系统,PHP,C#,Java等开发环境,以及很多的应用软件中,都可以看到正则表达式的影子。

应用领域广泛

Perl、PHP、Java、C++、C#、Python、 Xpath、.Net、

JavaScript、Jscript

Oracle、Mysql、Unix、Linux

不同语言的正则表达式总体上说是通用的,但不同语言间还是有一些差别,此文章主要通过PHP语言来详细介绍正则表达式。


二.PHP中的正则表达式

PHP提供了两套正则函数,来供开发者使用,两者功能差不多,分别为:

一套是由PCRE(Perl Compatible Regular Expression)库提供的,使用“preg_”为前缀命名的函数。

一套由POSIX(Portable Operating System Interface of Unix )扩展提供的,使用以“ereg_”为前缀命名的函数。(POSIX的正则函数库,自PHP 5.3以后,就不在推荐使用,从PHP6以后,就将被移除)

由于POSIX正则即将推出历史舞台,所以这里重点介绍PCRE正则的使用。

PCRE全称为Perl Compatible Regular Expression,意思是Perl兼容正则表达式。

PCRE所提供的正则表达式函数如下:


这些函数的具体功能,可以通过php手册来详细了解。

注:ereg系列的正则表达和preg有不少区别,比如ereg系列的正则表达式不需要定届符,preg系列的才需要,因此本章中所提及的语法,主要以preg系列为准。


三.正则表达式语法

正则表达式作为一个匹配的模板,是由原子(普通字符,例如字符a到z)、有特殊功能的字符(称为元字符,例如*、+和?等)以及模式修正符三部分组成的文字模式。一个最简单的正则表达式模式中,至少也要包含一个原子,如“/a/”。

我们看一下这个公式:/原子和元字符/模式修正符

也就是说,正则表达式的原子和元字符都放在定界符之间,而模式修正符放在定界符之外。

1.定界符

除了字母、数字和反斜线\以外的任何字符都可以为定界符号,比如 | |、//、{}、!!等等,但是需要注意,如果没有特殊需要,我们都使用正斜线//作为正则表达式的定界符号。

注:在与Perl兼容的正则表达式函数中使用模式时,一定要给加上定界符,即将模式包含在两个反斜线“/”之间。

2.原子

原子是正则表达式的最基本组成单位,而且必须至少要包含一个原子。

原子的构成如下:

1,所有打印(所有可以在屏幕上输出的字符串)和非打印字符(看不到的,比如空格,换行符等等)
2,如果所有有意义的字符,想做为原子使用,统统使用“\”转义字符进行转义即可。如:\. \* \+ \? \( \<\>。
注:" \ "转义字符可以将有意义的字符转成没意义的字符,还可以将没意义的字符转为有意义的字符。如:\d表示任意一个十进制的数字。
3,在正则表达式中可以直接使用一些系统提供的代表范围的原子,如下面的表格所示: 

代表范围的原子说明自定义原子表示法
\d表示任意一个十进制的数字[0-9]
\D表示任意一个除数字这外的字符[^0-9]
\s表示任意一个空白字符,空格、\n\r\t\f[\n\r\t\f ]
\S表示任意一个非空白[^\n\r\t\f ]
\w表示任意一个字 a-zA-Z0-9_[a-zA-Z0-9_]
\W表示任意一个非字,除了a-zA-Z0-9_以外的任意一个字符[^a-zA-Z0-9_]
4,自定义原子表(使用方括号[]),可以匹配方括号中的任何1个原子。

在上面的表格中我们已经将系统提供的范围原子使用自定义的方式作了等价转换。由于系统不可能提供所有我需要的原子,所以自定义原子表就显得十分必要了,比如我们想要匹配字母或者数字,就需要将原子写成[a-zA-Z0-9]。

注:自定义原子表中的原子有一个被字符串匹配上,就匹配成功了。而去掉自定义原子表的方括号,则表示匹配整个字符串。如'/abc/'表示字符串中必须有abc这个子串才能被匹配,而'/[abc]/'表示字符串中只要包含a、b和c中的任何一个字符,即被匹配。

这里需要注意:
A,符号“-”表示范围,如[a-z]表示小写字母a到z,但千万不要写成[a-9]这种形式!
B, 符号“^”表示取反,一定要放在方括号的开头,比如我们想要匹配非数字,则原子为[^0-9]。

3.元字符

所谓元字符,就是用于构建正则表达式的具有特殊含义的字符,例如的“*”、“+”、“?”等。如果要在正则表达式中包含元字符本身,使其失去特殊的含义则必须在前面加上“\”进行转义。

下面列出来正则表达式的元字符表:

字符说明
\将下一字符标记为特殊字符、文本、反向引用或八进制转义符。例如,“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 次。例如,“o{2}”与“Bob”中的“o”不匹配,但与“food”中的两个“o”匹配。
{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”在内的任意字符,请使用诸如“[\s\S]”之类的模式。
(pattern)匹配 pattern 并捕获该匹配的子表达式。可以使用 $0…$9 属性从结果“匹配”集合中检索捕获的匹配。若要匹配括号字符 ( ),请使用“\(”或者“\)”。
(?:pattern)匹配 pattern 但不捕获该匹配的子表达式,即它是一个非捕获匹配,不存储供以后使用的匹配。这对于用“or”字符 (|) 组合模式部件的情况很有用。例如,’industr(?:y|ies) 是比 ‘industry|industries’ 更经济的表达式。
(?=pattern)执行正向预测先行搜索的子表达式,该表达式匹配处于匹配 pattern 的字符串的起始点的字符串。它是一个非捕获匹配,即不能捕获供以后使用的匹配。例如,’Windows (?=95|98|NT|2000)’ 匹配“Windows 2000”中的“Windows”,但不匹配“Windows 3.1”中的“Windows”。预测先行不占用字符,即发生匹配后,下一匹配的搜索紧随上一匹配之后,而不是在组成预测先行的字符后。
(?!pattern)执行反向预测先行搜索的子表达式,该表达式匹配不处于匹配 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”范围内的任何字符。
\b匹配一个字边界,即字与空格间的位置。例如,“er\b”匹配“never”中的“er”,但不匹配“verb”中的“er”。
\B非字边界匹配。“er\B”匹配“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。
\n换行符匹配。等效于 \x0a 和 \cJ。
\r匹配一个回车符。等效于 \x0d 和 \cM。
\s匹配任何空白字符,包括空格、制表符、换页符等。与 [ \f\n\r\t\v] 等效。
\S匹配任何非空白字符。与 [^ \f\n\r\t\v] 等效。
\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 是一个正整数。到捕获匹配的反向引用。例如,“(.)\1”匹配两个连续的相同字符。
\n标识一个八进制转义码或反向引用。如果 \n 前面至少有 n 个捕获子表达式,那么 n 是反向引用。否则,如果 n 是八进制数 (0-7),那么 n 是八进制转义码。
\nm标识一个八进制转义码或反向引用。如果 \nm 前面至少有 nm 个捕获子表达式,那么 nm 是反向引用。如果 \nm 前面至少有 n 个捕获,则 n 是反向引用,后面跟有字符m。如果两种前面的情况都不存在,则 \nm 匹配八进制值 nm,其中 和 m 是八进制数字 (0-7)。
\nml当 n 是八进制数 (0-3),m 和 l 是八进制数 (0-7) 时,匹配八进制转义码 nml
\un匹配 n,其中 n 是以四位十六进制数表示的 Unicode 字符。例如,\u00A9 匹配版权符号 (©)。

4.模式修正符

1. 模式修正符就是几个字母,我们在每个正则表达式中可以一次使用一个,也可以连续使用多个,每一个具一定的意义。
2. 模式修正符是对整个正则表达式调优使用,也可以说是对正则表达式功能的扩展。
还记得正则表达式的那个公式吗?'/原子和元字符/模式修正符',其中正斜线为边界符。

模式修正符说明
i表示在和模式进行匹配进不区分大小写
m将模式视为多行,使用^和$表示任何一行都可以以正则表达式开始或结束
s如果没有使用这个模式修正符号,元字符中的"."默认不能表示换行符号,将字符串视为单行
x表示模式中的空白忽略不计
e正则表达式必须使用在preg_replace替换字符串的函数中时才可以使用(讲这个函数时再说)
A以模式字符串开头,相当于元字符^
Z以模式字符串结尾,相当于元字符$
U正则表达式的特点:就是比较“贪婪”,使用该模式修正符可以取消贪婪模式

5.分组语法

分类代码/语法说明
捕获(exp)匹配exp,并捕获文本到自动命名的组里
(?<name>exp)匹配exp,并捕获文本到名称为name的组里,也可以写成(?'name'exp)
(?:exp)匹配exp,不捕获匹配的文本,也不给此分组分配组号
零宽断言(?=exp)匹配exp前面的位置
(?<=exp)匹配exp后面的位置
(?!exp)匹配后面跟的不是exp的位置
(?<!exp)匹配前面不是exp的位置
注释(?#comment)这种类型的分组不对正则表达式的处理产生任何影响,用于提供注释让人阅读

6.贪婪与懒惰

当正则表达式中包含能接受重复的限定符时,通常的行为是(在使整个表达式能得到匹配的前提下)匹配尽可能多的字符。以这个表达式为例:a.*b,它将会匹配最长的以a开始,以b结束的字符串。如果用它来搜索aabab的话,它会匹配整个字符串aabab。这被称为贪婪匹配。

有时,我们更需要懒惰匹配,也就是匹配尽可能少的字符。前面给出的限定符都可以被转化为懒惰匹配模式,只要在它后面加上一个问号?。这样.*?就意味着匹配任意数量的重复,但是在能使整个匹配成功的前提下使用最少的重复。现在看看懒惰版的例子吧:

a.*?b匹配最短的,以a开始,以b结束的字符串。如果把它应用于aabab的话,它会匹配aab(第一到第三个字符)ab(第四到第五个字符)

代码/语法说明
*?重复任意次,但尽可能少重复
+?重复1次或更多次,但尽可能少重复
??重复0次或1次,但尽可能少重复
{n,m}?重复n到m次,但尽可能少重复
{n,}?重复n次以上,但尽可能少重复

四.程序实例

php函数的使用方法请具体查看php手册。

1.匹配邮箱

<?php
$email = 'test@yahoo.com.cn';
preg_match('/^[\w\-\.]+@[\w\-]+(\.\w+)+$/', $email, $result);
print_r($result);
?> 
首先来看下正则模式
/^[\w\-\.]+@[\w\-]+(\.\w+)+$/
左右两边的’/‘即是文章所提到的定界符,^和$分别表示匹配字符串的开头和结尾[\w\-\.]+表示匹配任何字类字符,下划线,’-‘以及’.‘。后面的加号表示匹配一次或多次。

注:[]表示只要匹配其中的一个字符,比如[abc]表示匹配任意一个a,b或c,而()则要匹配所有,比如(abc)表示要匹配’abc‘而不是任意一个。

该段代码输出为

Array( [0] => test@yahoo.com.cn [1] => .cn)
数组[0]为匹配到的字符串,[1]为第一个捕获子组匹配到的文本即是(\.\w+)里面的内容

2.匹配一串URL

<?php
$regex = '/^http:\/\/([\w.]+)\/([\w]+)\/([\w]+)\.html$/i';
$str = 'http://www.youku.com/show_page/id_ABCDEFG.html';
$matches = array();
if(preg_match($regex, $str, $matches)){
    print_r($matches);
}
?>
最后的i即是文章提到的模式修正符,表示匹配不区分大小写,’\/‘是用转义来匹配’/‘符号。

该段代码输出为

Array ( [0] => http://www.youku.com/show_page/id_ABCDEFG.html [1] => www.youku.com [2] => show_page [3] => id_ABCDEFG ) 

[1],[2],[3]分别是匹配3个括号内的子组。

3.分支条件

<?php
$test = '54545-5454';
preg_match('/\d{5}-\d{4}|\d{5}/', $test, $result);
print_r($result);
?>

此正则用于匹配美国的邮政编码。美国邮编的规则是5位数字,或者用连字号间隔的9位数字。'|'执行类似逻辑上的或操作,即x|y,匹配 x 或 y

该段代码输出为

Array ( [0] => 54545-5454 ) 

注:注意各个条件的顺序,类似语法上的或操作,将会从左到右地测试每个条件,如果满足了某个分支的话,就不会去再管其它的条件了。

因此,如果将代码反过来写,\d{5}|\d{5}-\d{4},则只会匹配5位的邮编以及9位邮编的前5位,因为不管是5位数字,还是用连字号间隔的9位数字,第一个分支匹配总是成立的。

4.分组

<?php
$test = '192.168.1.1';
preg_match('/(\d{1,3}\.){3}\d{1,3}/', $test, $result);
print_r($result);
?>

该段代码输出为

Array ( [0] => 192.168.1.1 [1] => 1. ) 

用小括号来指定子表达式(也叫做分组),然后你就可以指定这个子表达式的重复次数了,你也可以对子表达式进行其它一些操作。

(\d{1,3}\.){3}\d{1,3}是一个简单的IP地址匹配表达式。要理解这个表达式,请按下列顺序分析它:\d{1,3}匹配1到3位的数字(\d{1,3}\.){3}匹配三位数字加上一个英文句号(这个整体也就是这个分组)重复3次,最后再加上一个一到三位的数字(\d{1,3})。

4.后向引用

<?php
$test = 'go go';
preg_match('/\b(\w+)\b\s+\1\b/', $test, $result);
print_r($result);
?>
该段代码输出为

Array ( [0] => go go [1] => go ) 
使用小括号指定一个子表达式后, 匹配这个子表达式的文本(也就是此分组捕获的内容)可以在表达式或其它程序中作进一步的处理。默认情况下,每个分组会自动拥有一个 组号,规则是:从左向右,以分组的左括号为标志,第一个出现的分组的组号为1,第二个为2,以此类推。
向引用用于重复搜索前面某个分组匹配的文本。例如, \1代表 分组1匹配的文本

\b(\w+)\b\s+\1\b可以用来匹配重复的单词,像go go, 或者kitty kitty。这个表达式首先是一个单词,也就是单词开始处和结束处之间的多于一个的字母或数字(\b(\w+)\b),这个单词会被捕获到编号为1的分组中,然后是1个或几个空白符(\s+),最后是分组1中捕获的内容(也就是前面匹配的那个单词)(\1)。

你也可以自己指定子表达式的组名,请看如下代码:

<?php
$test = 'go go';
preg_match('/\b(?<Word>\w+)\b\s+\k<Word>\b/', $test, $result);
print_r($result);
?>
该段代码输出为
Array ( [0] => go go [Word] => go [1] => go ) 
要指定一个子表达式的组名,请使用这样的语法: (?<Word>\w+)(或者把尖括号换成 '也行: (?'Word'\w+)),这样就把 \w+的组名指定为 Word了。要反向引用这个分组 捕获的内容,你可以使用 \k<Word>,所以上一个例子也可以写成这样: \b(?<Word>\w+)\b\s+\k<Word>\b

5.零宽断言

用于查找在某些内容(但并不包括这些内容)之前或之后的东西,也就是说它们像\b,^,$那样用于指定一个位置,这个位置应该满足一定的条件(即断言),因此它们也被称为零宽断言

<?php
$test = 'testing';
preg_match('/\b\w+(?=ing\b)/', $test, $result);
print_r($result);
?>
(?=exp)也叫 零宽度正预测先行断言,它 断言自身出现的位置的后面能匹配表达式exp。代码中 \b\w+(?=ing\b),匹配 以ing结尾的单词的前面部分(除了ing以外的部分)

该段代码输出为

Array ( [0] => test ) 
匹配到以ing结尾的单词testing中test部分。

<?php
$test = 'reset';
preg_match('/(?<=\bre)\w+\b/', $test, $result);
print_r($result);
?>
(?<=exp)也叫 零宽度正回顾后发断言,它 断言自身出现的位置的前面能匹配表达式exp。代码中 (?<=\bre)\w+\b会匹配 以re开头的单词的后半部分(除了re以外的部分)

该段代码输出为

Array ( [0] => set ) 
匹配到以re开头的单词reset中set部分。

注:需要注意的是,PHP中的零宽断言是不支持不定长度的匹配的,即是不能使用'+','*'等不确定长度的正则。例如(?<=(r{1,3}))\w+,用来匹配以1到3个r开头的字符串,这样的写法在PHP中是会报错误的。

6.负向零宽断言

用来匹配某个条件不出现的情况。比如我们想匹配不以Ing为结尾的单词。

<?php
$test = 'rrraavv';
preg_match('/\w+(?!ing)/', $test, $result);
print_r($result);
?>
零宽度负预测先行断言 (?!exp)断言此位置的后面不能匹配表达式exp

该段代码输出为

Array ( [0] => rrraavv ) 
匹配到不以ing为结尾的字符串rrraavv。

<?php
$test = '5485625';
preg_match('/(?<![a-z])\d{7}/', $test, $result);
print_r($result);
?>
同理,我们可以用 (?<!exp), 零宽度负回顾后发断言断言此位置的前面不能匹配表达式exp

该段代码输出为

Array ( [0] => 5485625 ) 
匹配 前面不是小写字母的七位数字

7.注释

小括号的另一种用途是通过语法(?#comment)来包含注释。例如:2[0-4]\d(?#200-249)|25[0-5](?#250-255)|[01]?\d\d?(?#0-199)

8.递归匹配

有时我们需要匹配许多嵌套的组,要实现这一的功能,需要使用递归。

<?php
echo '<pre>';
$test = 'xx {aa {bbb} }{bbb} aa} yy';
preg_match_all("/{([^{}]+|(?R))*}/", $test, $result);
print_r($result);
?>
上面的正则表达式中的关键点是 (?R),该符号代表整个正则表达式本身,这就是递归所在。

该段代码输出为

Array
(
    [0] => Array
        (
            [0] => {aa {bbb} }
            [1] => {bbb}
        )

    [1] => Array
        (
            [0] =>  
            [1] => bbb
        )

)

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值