1:什么是正则表达式?
正则表达式是对字符串操作的一种逻辑公式。完整的正则表达式由两种字符构成:元字符(metacharacters,提供强大的描述能力)和文字(literal)。
基本元字符
. 匹配任意单个字符
| 逻辑或操作符
() 限定多选结构,分组, 捕获文本
[] 定义一个字符集合,匹配该集合中的一个字符
[^] 对字符集合求非(是对整个集合求非,而不是紧挨着^符号的字符)
- 在于符集合中定义一个区间。如[A-Za-z],但是如果是在[]外面或者[-abc]就不表示元字符。
\ 对下一个字符转义。比如/n表示换行。
匹配位置元字符
^ 匹配一行开头
$ 匹配一行结尾
数量元字符
* 匹配前一个字符(子表达式)零次或多次
*? *的懒惰型版本(防止正则表达式的“贪婪性”)
+ 匹配前一个字符或子表达式一次或多次
+? +的懒惰型版本
? 匹配前一个字符或子表达式零次或一次
{n} 匹配前一个字符或子表达式的n次重复,比如[A-Z]{6}表示匹配由六个大写字母组成的字符串。
{m,n} 匹配至少m次至多n次
{m,} 匹配至少m次
{m,}? {m,}的懒惰型版本
附一张表格列出基本的正则元字符在常用工具中的表示法。
PCRE记法 |
vi/vim |
grep |
awk |
sed |
* |
* |
* |
* |
* |
+ |
\+ |
\+ |
+ |
\+ |
? |
\= |
\? |
? |
\? |
{m,n} |
\{m,n} |
\{m,n\} |
{m,n} |
\{m,n\} |
\b * |
\< \> |
\< \> |
\< \> |
\y \< \> |
(…|…) |
\(…\|…\) |
\(…\|…\) |
(…|…) |
(…|…) |
(…) |
\(…\) |
\(…\) |
(…) |
(…) |
\1 \2 |
\1 \2 |
\1 \2 |
不支持 |
\1 \2 |
|
附POSIX字符组
POSIX字符组 |
说明 |
ASCII语言环境 |
Unicode语言环境 |
[:alnum:]* |
字母字符和数字字符 |
[a-zA-Z0-9] |
[\p{L&}\p{Nd}] |
[:alpha:] |
字母 |
[a-zA-Z] |
\p{L&} |
[:ascii:] |
ASCII字符 |
[\x00-\x7F] |
\p{InBasicLatin} |
[:blank:] |
空格字符和制表符 |
[ \t] |
[\p{Zs}\t] |
[:cntrl:] |
控制字符 |
[\x00-\x1F\x7F] |
\p{Cc} |
[:digit:] |
数字字符 |
[0-9] |
\p{Nd} |
[:graph:] |
空白字符之外的字符 |
[\x21-\x7E] |
[^\p{Z}\p{C}] |
[:lower:] |
小写字母字符 |
[a-z] |
\p{Ll} |
[:print:] |
类似[:graph:],但包括空白字符 |
[\x20-\x7E] |
\P{C} |
[:punct:] |
标点符号 |
[][!"#$%&'()*+,./:;<=>?@\^_`{|}~-] |
[\p{P}\p{S}] |
[:space:] |
空白字符 |
[ \t\r\n\v\f] |
[\p{Z}\t\r\n\v\f] |
[:upper:] |
大写字母字符 |
[A-Z] |
\p{Lu} |
[:word:]* |
字母字符 |
[A-Za-z0-9_] |
[\p{L}\p{N}\p{Pc}] |
[:xdigit:] |
十六进制字符 |
[A-Fa-f0-9] |
[A-Fa-f0-9] |
|
2:正则表达式匹配过程。
正则表达式两条普遍适用的原则
1,优先选择左端的匹配结果。
2,标准的匹配量词是优先匹配的。(如*,+,?,{m,n})
规则一对于egrep这类只关心是否匹配而不关心位置的这类程序来说是无关紧要的,但是对于查找和替换这个一定要小心:如
[root@localhost ~]# cat angus
angus blog http://angus717.blog.51cto.com/
angus gus
blog
http://angus.blog.51cto.com/index.html
如果需要匹配gus ,此时第二行会优先匹配angus,这时你如用替换命令,就会和你想象中的不一样了
[root@localhost ~]# sed 's/gus/nus/' angus
annus blog http://angus717.blog.51cto.com/
annus gus
blog
http://annus.blog.51cto.com/index.html
对于第二条,还有个过度匹配优先。
现在把angus文件修改下,用http:\/\/.*index.html来匹配 http://angus.blog.51cto.com/index.html
[root@localhost ~]# cat angus
angus blog http://angus717.blog.51cto.com/
angus gus
blog
http://angus.blog.51cto.com/index.html Regular Expressions
这个匹配会首先匹配http://然后.*会把整行匹配完,一直到Expressions这地方。但是index.html必须匹配,所以.*匹配的内容必须从后向前交出一下字符来匹配l,从末尾的s开始直到Regular的l时候这时匹配l了,接着继续匹配m,很明显u不能匹配l,继续向前匹配l,直到匹配index.html。
3:正则表达式引擎:
什么是正则表达式的引擎,对于上个例子说http:\/\/.*index.html这个就是引擎。
正则表达式引擎一般都是有文字字符,量词,字符组,括号等按照一定方式组合起来的。
正则引擎大体上可分为不同的两类:DFA和NFA,而NFA又基本上可以分为传统型NFA和POSIX NFA。
DFA Deterministic finite automaton 确定型有穷自动机
NFA Non-deterministic finite automaton 非确定型有穷自动机
DFA引擎因为不需要回溯,所以匹配快速,但不支持捕获组,所以也就不支持反向引用和$number这种引用方式,目前使用DFA引擎的语言和工具主要有awk、egrep 和 lex。
POSIX NFA主要指符合POSIX标准的NFA引擎,它的特点主要是提供longest-leftmost匹配,也就是在找到最左侧最长匹配之前,它将继续回溯。同DFA一样,非贪婪模式或者说忽略优先量词对于POSIX NFA同样是没有意义的。
大多数语言和工具使用的是传统型的NFA引擎,它有一些DFA不支持的特性:
捕获组、反向引用和$number引用方式;
环视(Lookaround,(?<=…)、(?<!…)、(?=…)、(?!…)),或者有的有文章叫做预搜索;
忽略优化量词(??、*?、+?、{m,n}?、{m,}?),或者有的文章叫做非贪婪模式;
占有优先量词(?+、*+、++、{m,n}+、{m,}+,目前仅Java和PCRE支持),固化分组(?>…)
PCRE和POSIX
常见的正则表达式记法,其实都源于Perl,实际上,正则表达式从Perl衍生出一个显赫的流派,叫做PCRE(Perl Compatible Regular Expression),『\d』、『\w』、『\s』之类的记法,就是这个流派的特征
POSIX的全称是Portable Operating System Interface for uniX,它由一系列规范构成,定义了UNIX操作系统应当支持的功能,所以“POSIX规范的正则表达式”其实只是“关于正则表达式的POSIX规范”,它定义了BRE(Basic Regular Expression,基本型正则表达式)和ERE(Extended Regular Express,扩展型正则表达式)两大流派。在兼容POSIX的UNIX系统上,grep和egrep之类的工具都遵循POSIX规范,一些数据库系统中的正则表达式也符合POSIX规范。
DFA 和 NFA 反映了将正则表达式在应用算法上的根本区别。NFA为:"表达式主导(regex-directed表达式的控制权在不同元素间切换)引擎";DFA为:"文本主导(text-directed)引擎"
NFA实质上,在表达式主导匹配过程中,每一个子表达式都是独立的。这不同于反向引用,子表达式之间不存在内在联系,而只是整个正则表达式的各个部分。与NFA不同,
DFA引擎在扫描字符串时,会记录“当前有效”的所以匹配可能。举个例子说明下,这个例子本身没有实在意义。
用 some[then|where|body]来匹配这句话“there are something to do by somebody. ”
NFA的处理过程是在字符中先找到s,标记为备用位置,然后先处理最先出现s的something,检查是否匹配o,接着匹配m,接着匹配e,此时是检查是否匹配t,从上面例子看出something匹配字符t和字符h,到字符e的时候,很明显i不能匹配,所以NFA引擎的回溯功能会回到some的e字符处开始匹配where,很明显匹配失败,然后继续返回匹配body,也是失败,此时something匹配不成功,继续向后匹配,同样的过程匹配somebody成功。
对应DFA的处理过程,它想从there开始匹配s, 定位到s,检查后面是否有o,m,e。然而e后面是t,所以只匹配then中的t(where和body)已经匹配失败,接着匹配h,接着匹配i,很显然then也匹配失败,继续向后匹配,然后匹配到somebody。
关于回溯借用http://su1216.iteye.com/blog/1662046的这个例子看起来更明白点。
3,固化分组
固化分组的格式(?>...)
对于“(?>...)”中的内容部分(省略号省略的部分)来说,与之前的匹配规则一致,没有什么区别,但是,当此部分表达式匹配完毕,开始匹配括号外面的部分时,括号内的所有备用状态都会被放弃,也就是说,在固化分组匹配结束时,它已经匹配的文本已经固化为一个单元,只能作为整体而保留或放弃。括号内的子表达式中未尝试过的备用状态都不复存在了,所以回溯永远也不能选择其中的状态(至少是,当此结构匹配完成时,“锁定(locked in)”在其中的状态)
固化分组到底有什么用处呢?
原来格式为123.456,后来因为浮点数显示问题,部分数据格式变为123.456000000789这种,,要求做到只保留小数点后面2-3位,但是,最后一位不能为0,这个正则如何写呢?(下面直接考虑小数点后面的数字),写出正则之后,我们还要用这个正则去匹配数据,把原来的数据替换成匹配的结果。
我们可以立刻写出这样的正则【\.\d\d[1-9]?\d*】,PHP代码为
$str = preg_replace('\.(\d\d[1-9]?)\d*','\\1',$str);
很明显,这种写法,对于部分数据格式为123.456的这种格式,白白的处理了一遍,为了提高效率,我们还要对这个正则进行处理。从123.456这个字符串跟其他的比较一下,我们发现,是疑问123.456这个数据后面没数字了,所以,白白处理一遍。那好办,我们对这个正则改造一下,把后面的量词*改成+,这样对于123.45 小数点后面1,2位数字的,不会去白白处理,而且,对三位以上数字的,处理正常。其PHP代码为
$str = preg_replace('\.(\d\d[1-9]?)\d+','\\1',$str);
对于上面匹配过程,首先(小数点前123不说了),【\.】匹配".",匹配成功,把控制权给下一个【\d】,【\d】匹配“4”成功,把控制权给第二个【\d】,这个【\d】匹配“5”成功,然后,把控制权给了【[1-9]?】,由于量词是【?】,正则表达式遵循“量词优先匹配”,而且,此处是【?】,还会留下一个回溯点。然后匹配"6"成功,然后把控制权给【\d+】,【\d+】发现后面没字符了,最遵循“后进先出”规则,回到上一个回溯点,进行匹配,这时,【[1-9]?】会交还出其匹配的字符“6”,【[1-9]?】匹配“6”成功。匹配完成了。结果发现【(\d\d[1-9]?)】匹配的结果确是"45",并不是我们想要的“456”,“6”被【\d+】匹配去了。
能否让【[1-9]?】匹配一旦成功,不进行回溯呢?这就用到了我们上面说的"固化分组", PHP(preg_replace函数)中使用的正则引擎支持固化分组,我们根据固化分组的写法,可以把代码改成如下方式
$str = preg_replace('\.(\d\d(?>[1-9]?))\d+','\\1',$str);
【\.】匹配".",匹配成功,把控制权给下一个【\d】,【\d】匹配“4”成功,把控制权给第二个【\d】,这个【\d】匹配“5”成功,然后,把控制权给了【(?>[1-9]?)】,由于,正则表达式遵循“量词优先匹配”,所以【[1-9]?】匹配6,由于此时是固化分组匹配到的6,所以,此处是【?】,不会留下一个回溯点。然后把控制权给【\d+】,【\d+】发现后面没字符了,前面又没有回溯点,所以整体匹配也就视频,"0.625"不需要处理这正是我们需要的。(精通正则表达式p170页)。
4,环视
环视格式
(?=exp) 匹配exp前面的位置
(?<=exp) 匹配exp后面的位置
(?!exp) 匹配后面跟的不是exp的位置
(?<!exp) 匹配前面不是exp的位置
示例:目标:将字符串中 1234567 这样的数字变成这种形式: 1,234,567 (精通正则表达式p59页)
需要处理的字符串The US population is 298444215,处理的php代码
$str = 'The US population is 298444215'
$regex = '/(?=(\d\d\d)+)/';
echo preg_replace($regex, ',', $str); //输出:The US population is ,2,9,8,4,4,4,215
因为我们没有加一个结尾限定符,因此从 215 往前的每一位数字后面都有 3个数字,于是在前面的每一位就都加了一个逗号。
第一次修该
$regex = '/(?=(\d\d\d)+$)/'; //=就是加了一个 $ ,匹配字符串结尾
echo preg_replace($regex, ',', $str); //输出:The US population is ,298,444,215
很明显这个也不符合我们要求,逗号前必须有一位数字
第二次修改
$regex = '/(?<=\d)(?=(\d\d\d)+$)/';
echo preg_replace($regex, ',', $str); //输出:The US population is 298,444,215
看似很正确的样子
$str=将字符串中 1234567 这样的数字变成这种形式。
$regex = '/(?<=\d)(?=(\d\d\d)+$)/';
echo preg_replace($regex, ',', $str); //输出:将字符串中 1234567 这样的数字变成这种形式。
显然这个不符合我们要求,我们只需要找到一个位置,此位置的后面不是一个数字就可以了
$regex = '/(?<=\d)(?=(\d\d\d)+(?!\d))/';
echo preg_replace($regex, ',', $str); //输出:将字符串中 1,234,567 这样的数字变成这种形式。
继续整理中!!!!