Chinaunix首页 | 论坛 | 博客
  • 博客访问: 313072
  • 博文数量: 174
  • 博客积分: 3061
  • 博客等级: 中校
  • 技术积分: 1740
  • 用 户 组: 普通用户
  • 注册时间: 2006-05-04 22:43
文章分类

全部博文(174)

文章存档

2011年(54)

2010年(14)

2009年(30)

2008年(26)

2007年(27)

2006年(23)

我的朋友

分类: LINUX

2007-03-04 09:10:00

文件: small_test.rar
大小: 0KB
下载: 下载
正则表达式的匹配原则
1. 正则表达式是不理解输入的上下文含义的只是进行忠实的匹配.
2. 如果你知道明确的上下文,那么请在正则表达式中考虑那些你不想匹配的情况.
 
字符/字节/编码
字符在计算机领域内是一个有着特别含义的词. 一个字节所代表的字符取决于计算机如何来理解. 而这种理解方式称为编码, 或者是视角.
 
比如一个字节可以被理解为一个字符,也可以理解为某个字符的一部分,甚至同一个字节,或者同几个字节可以理解为不同的字符。 因此在正则表达式的处理中,需要确认目前的编码方式(视角)和当前使用工具的视角一致.
 
假如以语言作为类比,那么假如正则表达式是一种语言,那么正则表达式有两种字符,一种是普通字符,一种是特殊字符组成. 其中特殊字符也成为元字符,在一门语言中它们更像是一种语法而普通字符组成了文本.
 
行的开始和结束.
^$ 来表示行的开始和结束.
^hello   
^hello$
^
 
^hello , 似乎理解为以hello开始的行,不过按照正则的思路最好理解为以h开始的行且后面跟着e l l o.
单独一个^ 本身并没有实用意义,因为任何一个行都有开始.
 
在正则表达式的原字符中,^$ 我把它们理解为位置形特殊字符,也就是说它们只匹配位置不匹配文本.
 
字符组.
字符组的说明:
[] 内部就表示字符组,字符组内部的字符非常特殊,除了非起始位置的 - 是特殊字符,其它符号在内部都不算作特殊符号,比如.*\b 等表示 任意字符,任意数量,单词边界在字符组内都会到了字符本来的含义.
具体关于字符组可以参考 mastering regular expression 3rd page 9.
比如$在字符组内部就不再表示结束符号,而是表示perl的变量起始标记;如果要表示纯粹的字符,那么需要转义. @也是类似的情况.
 
另外一个在字符组内有特殊意义的字符时出现在开始位置的^它表示整个字符组是一个非类型字符组.
我们来看一个例子
h[^h] 能否匹配 such , che
 
字符组支持减法操作
比如[[abcd]-[ab]] == [cd]
 
字符组支持 AND 操作
[[abcd] && [cd]] == [cd]
 
 
 
如果在字符组内出现特殊字符组,那么如何呢?比如..
 
/([\w+]/ , 这个表达式来匹配 test , 这个文本$1 会使啥呢?
答案是t , 因为字符组内\w 是一个特殊字符组,依然有效,不会解释为\ w , 而+在这儿并没有特殊的含义,因此它的含义相当于(\w|\+).
 
che 确实能够被匹配. 不过such是否能够被匹配,就需要注意了.
对于大部分工具 che 后面含有一个$结束符号,那么能够被匹配,但是如果有些工具去掉了这些符号
那么就无法匹配.
 
这儿就要区分两个含义, [^h] 表示的是匹配一个不是h的字符,而不是"不匹配h".
语义如下: 匹配一个未列出的字符,而不要匹配列出的字符.
 
./dot
匹配任意一个字符.
见下面的/s /m option.
 
| / or
匹配任意子表达式.
hello | world , 表示或者匹配hello , 或者world.
 
single line mode(/s) : 这个名词实际上有点混绕,它真实的含义是对于输入不再区分是否有换行符,也就是说可以匹配任何字符。
 
multiline mode(/m):  一般来说,regex对于那些行内的回车换行,不进行匹配(^$),但是一旦开启了multiline mode,那么^$就能够匹配那些嵌入的换行符号. 比如我阅读的书籍举的例子就是 一个html的文件,它的一行实际上有很多回车换行,但是只是处于一行.
举例来说, ...tags.[NL][NL] ... tags .
~s/^$/

/mg , will be "...tags.[NL

[NL> ...tags. 也就是说空行将被替换成

 
mastering perl page 113 : 列出了大部分的modifier , 可以参考.
 
hel(l|o) 表示匹配hell or helo.
 
如果是 hel[l|0]  表示匹配 hell , hel| , helo , 注意哦,这儿的| 因为在字符组内因此不适元字符.
 
单词分隔符.
\<, \> ; \b
这个取决于各个宿主语言的流派flavor.
 
量词
? * + {min,max}
? 表示可以匹配一次或者不匹配.
* 表示匹配任意次或者不匹配
+ 表示匹配至少一次
{min,max} 表示至少匹配min次,最多max次.
 
这儿需要说明,这些修饰符在正则中成为优先匹配符,也就是说它们会尽可能的向上限进行匹配.
 
()的用法
1. 分割 | 的范围.
2. 将若干个字符组合为一个单元.
3. 反向引用.  不过这个不一定在所有的正则工具中能够得到支持.
 
比如
 
/([0-9]+)\s+$1/
如果123 123 那么就能够和这个发生匹配.
 
转义
\ .
如果\ 后面跟着元字符,那么将转换为本来含义。
如果\ 后面跟着非元字符,那么将取决于你使用工具而定.
 
\s 匹配任何空白字符,比如SPACE, TAB
 
\t  TAB
\n   换行
\r  回车
\s  任何空白
\S 除了\s之外的任何字符.
\w [a-zA-Z0-9]
\W 任何非\w的字符.
\d [0-9]
\D 任何非\d字符。
\c 这个时匹配一个byte但是要注意使用这个的时候得非常小心,必须明确输入就是可以这样搞得,否则就会有问题.
\X unicode 组合匹配模式,相当于\P{M}\p{m}* , 就是匹配一个基本字母然后后面接若干非基本字母字母. 这样就能够匹配带有重音符号等组合的字符.
 
 

unicode 基本标记

\p{L} , 表示一般的字母.

\p{M} , 表示这个时一个辅助字符,就是说它本身不能作为一个单独字符,必须和别的基本字符组合在一起才能构成一个字符.

\p{Z} , 表示分隔字符,比如各类空格等非可见字符.

\p{S] , 符号字符,表示各类标记,比如禁止吸烟啥的.

\p{N} , 表示数字字符.

\p{P} , 标点上的字符, 比如句号啥的.

\p{C} , 其他不常用的字符.

 

作为一个简单例子,比如

my $input = <>;
if($input =~ m/(\p{L}+)/)
{
        print "match".$1."\n";
}
exit;

假如输入test

这样$1 = test , 匹配了字母. 特殊字母不知该如何输入.

unicode 扩展标记 ,所谓扩展标记就是在基本标记基础上进行的扩展.

\p{Ll} , 表示小写的letter.

\p{Lu}, 表示答谢的letter.

\p{Lt} , 表示出现在一个单词第一个位置的字符,但是它是大写的. 对于这个不是很了解.

\p{L&} , 上述三种情况的组合字符集.

\p{Lm}, 所谓的modifier letter,不过不明白.

\p{Lo}

另外一些正常的缩略
\a  警报.
\b  退格
\e  escape
\f  进纸
\v  垂直制表
\t  水平制表
 
\i 修饰符,表示忽略大小写.
 
(?: ...) 表示放弃本()的捕获.
(?> ...) 表示此为固定分组.
 
 
八进制
采用八进制我们可以插入在平常正则表达式中很难输入的字符.比如escape \033 , 因为并不是所有的系统都支持\e 转移字符的. 
   一般来说八进制要求第一个字符为0比如\0xx , 但是如果允许一位数字的话,确实会和反向引用一致,在这种情况下,以反向引用优先.
 
16进制
\x \u \U
\x{...} 这种方式就可以允许出现任意多位16进制.
 
控制字符
\cchar
比如\cH ,匹配control-H ,表示退格.
 
posix 规定 \0 不能匹配NUL ,值为零的字符,不过大多数脚本语言中允许用. 好匹配NULL.
注意: (?...) 这种命名捕获perl是不支持的.
 
 
 
\b 的用法 : 在char-class外它表示匹配单词的开始结束,在char-class内部它表示escape.
 
\g,\G 的用法现在还是有点不明白.
 
$/ , 方式能够改变默认的 \n 方式的换行读取模式,但是还是不理解.
 
SPACE* | TAB*  vs  [SPACE TAB]* . 前者表示匹配一些列空格或者一系列TAB,后者支持混合匹配.
 
\t TAB
\n a newline character
\r a carrier-return
\s any "white space"
\S [^\s]
\w [a-zA-Z0-9_]
\W [^\w]
\d [0-9]
\D [^\d]
 
\A 类似于^ ,表示匹配字符串的开始,但是不同的是,\A式中匹配字符串开始,不管中间是否有多个行.
^ 在\m 多行模式打开的情况下将能够匹配中间的行开始.
 
\Z , $ 两者的方式类似.
\z 不管在何种模式下均表示字符串的最后.
 
\p{..} , \P{..} 表示对于UNICODE的属性设置,小写的和大写的表达的正好是相反的意思.
p 120. 有一张表解释了一些常用的用法.
 
/i ignore case , 假如是在子表达式中那么 (?i) 表示开启,(?-i)表示关闭.
?: if used in (?:) , it means no catching for $1,....$n.
 
/x 表示扩展模式或者叫做宽松模式,在这种情况下,非字符组内的空格,tab \s 将被忽略,因此在处理反向引用比如 \1 2 的时候再扩展模式下将变成\12 , 为了正确处理最好采用(\1)2
 
. 号匹配了所有字符,但是不能匹配 \n , 当然在\s single line mode下也匹配.
但是 ^ 的范围中将匹配 \n.
 
\Q...\E , 这种序列能够将中间的RE元字符含义取消,比如
m/$regx/ , 如果 $regx = c:\windows\ , 那么\w 将被解释为一个元字符,而有时候我们并不需要
这种特性,这样只要 m/\Q$regx\E/ , 就可以了.
 
() 有名称捕获的特性,这儿还存在一种命名捕获.
 
(?> ...) 原子匹配, 
(?> .*)! 将无法匹配 abc! , 因为原子匹配导致.* 不会放弃任何已经匹配的内容,因此.* 已经匹配了abc! 因此 (?>.*)! 将无法最终匹配.
 
? 表示 if then | else
 
(regex) ? then .... | else .
 
(]+>\s*) ?   # if 存在
   ]+>   # 那么尝试匹配IMG
 
这个例子需要注意第二个问号开始的包含在括号内的才是表示(?if then | else)
 
 
inplace edit eample
 
c:> perl -p -i.bak -e "s/test/milan/g" inputfilelist
so all words milan in filelist were replaced by test.
 
 
 
(?=XXX) 表示进行前向匹配,意思就是开始找出现XXX的地方,且位置就是XXX前的地方.
(?<=XXX) 表示进行后向匹配,意思就是开始找出现XXX的地方,且位置就是XXX后的地方.
 
举个例子给丁任意一串数字,比如它代表一个钱的数量,将它格式化为以,号隔开的字符串.
 
1.pl
$input = $ARGV[]0];
$input =~ s/(?<=\d)(?=(\d\d\d)+$)/,/g;
print $input;
 
c:\> perl  1.pl 12345678
12,345,678.
 
这个就是前向搜索和后向搜索的运用简单举例.
 
2007-12-18
对于perl结合re的任意层次递归读到的例子.
 
my $levelN;
$levelN = qr \ \"
                ((?:[^()] | (??{$levelN})*)
               \"
             \x;
 
while($input =~ m/$levelN/x)
{
     print "括号内容 = ".$1."\n";
     $input = $1;
}
 
上面这个例子能够匹配出给定一个输入中所有嵌套层次括号中的内容.
(??{$var}) , 这个用法是perl的一个特点.
在理解的时候可以将这个内容全部去掉的结果当成 n = 0 时候的值.
然后当 n = 1的时候就将 去掉的位置替换为 ??{$val}. 依次类推.
比如: $level0 = qr\ \( (?:[^()])* \) \x;
     这个表示仅仅能够匹配但层次括号.
那么如果要匹配多层次括号 n = 1.
$level1 = qr\ \( ((?:[^()] | $level0)*) \) \x;
$level2 = qr\ \( ((?:[^()] | $level1)*) \) \x;
........
 
另外一个嵌套模式如下,实际上是上述模式的一个特例,它是对表达式本某个部分的引用,而不是对匹配结果的引用.
$regex = qr/ (??{$1}(\w)/;
实际上这个表达式在匹配的时候等同于 \w\w , 当然第一个表示嵌入的括号不作为捕获,而作为对阵阵的第一个捕获的引用.
 
而前一个例子实际上表示对整个表达式的嵌入,因此就表现出一种循环特性.
 
 
2007-12-19 一个匹配"" 内部字符的例子,这个例子实际上不算太复杂,主要是学些一个思考的模式
1. 匹配开始字符,结束字符
2. 如果中间的内容有可能出现开始或者结束字符,那么需要特别处理.
3. 需要考虑不匹配情况的处理.
 
先来看,如果仅仅考虑1.
那么 $regex = qr/ "[^*]"/x;  这样就能够匹配,它的含义是匹配以"开始以"结束的,且中间是非"的字符串.
这个模式,对于一般的简单情况确实已经可以工作了。
 
再来看2 , 因为"" 内部也有可能出现", 比如 " this is a \" test" , 那么中间出现的"实际上被转义掉了,它不再是作为结束".
$regex=qr/" (\\" | [^*])* "/x; 
这个表达式确实能够匹配上面的那个例子,但是要知道 "this is a \\" test" , 这种情况这个"已经不是被转义掉了,而是就是作为结束".
因此特殊情况在处理的时候还是不够仔细,再次修改.
$regex = qr/" (\\. | [^"])* "/x;
这样就能够比较好的匹配,它匹配带有准一字符的任何字符,或者任意的非"字符.
 
一般来说到了这儿就差不多了,不过座位一个匹配,我们再来看3.
如果不匹配呢?
 
假如输入如下 "this is a \" test. 要知道这个表达式实际上没有结束"因此不应该产生匹配,但是上面那个表达式在搜索到结束的时候发现无匹配,就进行回溯, 上一个正确的状态是,
 
this is a +\"                +[^"]
 
回溯到上面这个状态. 这个时候由于^" 能够匹配\ , 那么匹配继续进行因为这个时候 \\. 无法匹配" , 但是 结束的"却成功匹配,因此最终""内部的内容匹配为, this is a \, 而这个实际上不是我们要的结果.
这个就是3说明的情况,就是说一个正确的re要考虑能够匹配情况下的结果,也要考虑不能匹配情况下的情况.
 
这儿从分析来看那,实际上发生了多余的回溯,因此可以从禁止回溯来解决.
$regex = qr/ " (\\. | [^"])*+"/x ;  # 如果支持贪婪匹配,匹配的内容不放弃
$regex = qr/ " (?> \\. | [^"])* "/x; # 如果支持固化分组,丢去之前成功的状态也行.
$regex = qr/ " ( \\. | [^\\"])* "/x; # 匹配任意转义字符,或者匹配任意非被转义"字符,这样的精确定义方式来避免.
 
我们再来看一个例子,匹配一个数字.允许负数. 如果是小数那么整数部分允许忽略.
根据第一条规则.
$regex = qr/ -?\d*(\.\d+)? /x;
这个表达式的含义是-可以有也可以没有,
后面可以跟任意个数字,数字后面可以有或者没有  小数点和至少一个数字.
上面这个表达式确实能够匹配  比如-1.2 , 1.2 , .2 , 3 这样的数字.
按照上面的描述,第二条规则在这儿不可能存在.
 
我们发现这个表达式出现的限定符号,全部是可以忽略的,意思就是说,如果我输入的根本就不是一个数字,由于? , *这类限定符号的存在,依然会发生匹配,这个现实不是我们所需要的.
假如说整数没有,那么必须存在小数,如果小数没有那么必须存在整数部分,这个就使从语言上来的说明.
$regex = qr/-? (\d+(\.\d+)? | \d*(\.\d+))/x;
这样一个表达式那么上面那个问题就解决了.
 
不过如果我们碰到下面这样的例子, 问题又来了.
123.123.123
这儿为了简单描述起见限定,要么是数字开头,或者前面是\s , 或者是数字结尾,或者后面是\s.
$regex=qr/(\s+ | ^)-? (\d+(\.\d+)? | \d*(\.\d+))($|\s+)/x;
这个表达式就能够从一系列文本中挑选出符合规定的数字.
如果实际运用中还有更加精确的上下文判断那么还可以改进.
 
2007-12-20 匹配主机名称
常见的url如下
当然结尾也有可能是htm结尾的.
 
hostname 部分我们可以用如下方式进行匹配, [-0-9a-zA-Z_]+ .
而path部分略复杂 [-a-zA-Z0-9_:@&?=+,.!/~*%$]* 这个就复杂些了.
 
考虑到实际运用中
我们暂且认为这些都是合法的.
我们的思路是先匹配那些不会变化的,部分比如 是必须有的.
然后后面的时可以变化的东西我们再分别匹配,这儿同样要考虑到不能将不适合的匹配也匹配.
 
hostname = [-a-zA-Z0-9_]+
path = [-a-zA-Z0-9_:@&?=+,.!/~%$]*
 
 
my $regex = qr#
                ^http://([-a-zA-Z0-9_]+)(/?|/[-a-zA-Z0-9_:@&=+,.!/~%\$]+
                (?:\.html?)?)$
              #x;
 
这个表达式前面部分 ^ 是匹配开始部分就是http:// ,当然这个地方没有考虑比如有前置空格的问题.
([-a-zA-Z0-9_]+ 表示hostname 的匹配,这儿至少要匹配一次.
 
后面部分就是变化的部分.
首先 /? 表示/ 可以出现或者不出现,但是如果不出现那么马上就碰到了$结尾,这儿没有考虑后置空格问题.
再看另外一条分支 /[[-a-zA-Z0-9_:@&=+,.!/~%\$]+(?:\.html?)?
这套分支表示如果/出现那么后面必须有path,至于 .html那么就可以出现也可以不出现,也可以以htm形式出现.
 
再来看一个匹配时间的问题 比如 09:19am 10:00pm 23:58 匹配一个合法时间的问题.
这儿有3种情况,就是分早上时间,下午时间,还有24小时制的时间.
 
其实在真正使用的时候,也可以考虑用如下比较简单的方式进行匹配.
 
(\d\d?):(\d\d)\s([a|p]m)? , 来匹配12小时制. 然后通过捕获$1,$2 , $3 来根据$3所捕获到的小时制式来结合$1,$2来判断hour, minute是否合法.
不过既然我们学习re那么这儿给出一个完全由正则表达式完全解决的方法.
 
1 . 如果是24小时制,那么小时的最大值能够到23.
    看分.
    十分位 , 0-5 , 分位 0-9.
    [0-5]?[0-9], 就可以表示分钟.
 
      先来看小时
       00-09 , 10-19 , 20-23
       也就是说第一位出现0,1,或者缺失的时候,第二位最大到9 , 第一位出现2的时候第二位最大到3.
       ([0-1]?[0-9] | 2[0-3]) 用这个来表示小时.
  
    结合起来,24小时制.就是
    ([0-1]?[0-9] | 2[0-3]):([0-5]?[0-9]).
2. 如果是12小时制.
   我们先来看上午的时间.
   分还是一样的。主要是小时.
   第一位如果是0或者缺失, 那么 0?[0-9] ,
   如果第一位是1 , 那么第二位  1[0-2].
   (0?[0-9] | 1[0-2])    下午时间同上午时间.
结合起来
  (0?[0-9] | 1[0-2])
  最后结合起来.
(?: (0?[0-9] | 1[0-2]) | ([0-1]?[0-9] | 2[0-3]) ) : ([0-5]?[0-9])\s[a|p]m
 
  最后考虑到不能匹配诸如
12:34amtest  或者0234:45pmxxx , 这类的.
因此我们这儿重新修改
 
(\s+|^)(?: (0?[0-9] | 1[0-2]) | (0?[0-9] | 1[0-9] | 2[0-3]) ) : ([0-5]?[0-9])\s[a|p]m(\s+|$)     
 
# 24 hour
my $regex24 = qr/(?:\s+|^)
                (0?[0-9] | 1[0-9] | 2[0-3])
                :
                ([0-5]?[0-9])
                (?:\s+|$)
              /x;
 
# 12小时版本
my $regex12 = qr/ (?:\s+|^)
                  (0?[0-9] | 1[0-1])
                  :
                  ([0-5]?[0-9])([a|p]m)(?:\s+|$)
                /x;
 
这样就提供了一套12小时制和24小时制的时间匹配模式!
 
 
再来看一个例子,来了解lookback , lookahead 功能。
题目是这样的,给定任意一个数字, 将他划分为d,ddd,....,ddd 这种模式.
 
 
比如有一个数字1234567 , 需要的结果是1,234,567.
 
我们先从简单的来. 假如输入始终是1234567.
$regex = /(\d)(\d\d\d)/$1,$2/ ;
这个表达式将得到1,234567. 或许会想那是因为没有循环的缘故,好再来.
为什么会这样的音位正则表达式匹配到 1,234567的时候所有的文本都已经匹配,因此不会再继续运行下去.
 
while($input =~ s/$regex/$1,$2/g)
{
    print $input."\n";
}
 
结果为.
1,234567
1,2,34567
1,2,3,4567
1,2,3,4,567.
 
呵呵,因为(\d\d\d)+ 匹配一次即可,并不是说一定要匹配完.
 
$regex = qr/(\d)(\d\d\d)+\D/x;
 
如果正则表达式改成这个样子,那么基本上就差不多了. 不过考虑到输入 nov.11,1980 这种例子中的1980显然不是需要被匹配的.可以对开始位置进行限制.
$regex = qr/(\s+|^)(\d)(\d\d\d)+\D/x;
当然实际使用中还可以再进行和实际输入相匹配的原则.
 
下面我们就介绍loolahead/lookback 这两个也是对位置进行匹配的元素,还记得^$这两个匹配开始和结束位置的元字符.
 
(?=...) (?!)  顺序lookahead, 前者表示肯定,后者表示否定
(?<=...) (?
 
它们表示什么意思呢?
(?=a) 表示匹配一个位置这个位置后面有个字符a.
(?!a) ........., 这个位置后面不是a.
(?<=a) 表示这个位置前面的字符是a.
(?
 
有了这样的说明之后,就得到如下正则表达式.
my $regex = qr/(?<=\d)(?=(?:\d\d\d)+\D)/x;
 
if($input =~ s/$regex/,/gx)
{
    print $input."\n";
}
 
这样就能够得到和上次类似的结果. 注意这儿不需要进行循环.
因为/g参数执行的是全局操作. 因此在所有的位置被找出来之前替换将一致进行下去.
 
再看一个稍负责的例子,能够将文本转换为html.
undef $/;
my $input;
$input = <>;
# replace specical char
$input =~ s/&/&/g;
$input =~ s/$input =~ s/>/>/g;
# paragraph the text
$input =~ s/^\s*$/

/mg;

# convert url into link
my $regex = qr#
                 (?:\s+|^)()
              #x;
$input =~ s#$regex#$1#gi;
undef $regex;
# convert email addr into link
my $regex = qr#
                (?:\s+|^)([-a-zA-Z0-9_.]+@[-a-zA-Z0-9_.]+)(?:\s+|^)
              #x;
$input =~ s#$regex#
$input = <>;
 
my $regex = qr/\b([a-zA-Z0-9_]+)\s+\1\b/x;
while($input =~ s/$regex/$1/gx)
{
   print $input."\n";
}
 
就完成了一个全局替换.
 
字符串语义和正则语义的体会.
TAB 的二进制表示\t , * 的二进制表示 \x2A.
我们来看看  
 
   [\t\x2A]    []  "\t\x2A"    ""
 作为字符  [TAB*]      [\t\x2A]    "TAB*"      "\t\x2A"
 作为正则  [TAB*]      [\t\x2A]  "TAB*"      "\t\x2A"
 match  tab or *    tab or start  任意tab      TAB*
 /x  same  非法         TAB*
 
从这个表格我们可以看到字符串语义和正则语义的区别.
 
[\t\x2A] 与 [TAB*] 在正则表达式的环境下表达的含义是一致的.
"TAB*"  "\t\x2A"  在松散格式的正则表达式下表示的含义是不同的,因为TAB将会被忽略,而\t
则还是TAB的意思.
 
 
关于DFA/NFA 的测试.
2007-12-24 对于perl/deelx 的一个测试.
表达式为 X(.+)+X
输入的测试文本为=X===============..X
这儿的..表示很大量的==.
 
还有一个是cat | cat dog 来匹配文本cat dog , 如果cat匹配了肯定是NFA ,否则不是NFA,可能是
posix NFA or DFA. 从这个角度判断MTracer 2.0 肯定是 NFA.
 
结果在我的p4 3.0g 机器上果然perl 的NFA模式导致的大量回溯导致了长时间无法返回,而在deelx 上
进行的测试则返回很快.
 
正则的modifier中有一个\G 以及和它一起配合使用的\c 参数,现在找个例子来详细体验一下.
在这个例子中我们测试一个html文件片断,这儿只允许出现 img 标签且检查关闭配对.
另外荣誉出现文字,标点等non-html staff , 允许出现&xxx ,这类
此文件是utf-8编码,含有中文.
见附件 small_test.rar .
 
之前做过一个例子说是如何匹配引号内的内容,现在再来看一个相似的例子,如何匹配标签内的内容.
比如 content ,现在就要匹配 content .
想起来前面做引号匹配的时候用到了 ^" , 但是由于tag有多个字符,不能采用这种方式,不过可以马上
想到lookround .  (?=) 这样的结购.
my $input = <>;
my $regex = qr#
                ((?:(?!).)*)
              

               #x;
if($input =~ m/$regex/x)
{
        print "match".$1."\n";
}
 
这个例子就完成了这个任务,不过这个方案效率还是需要优化的. 这个和lookround 的效率有关.
 
 
下面的例子是匹配小数点后的数字,如果<=2位那么保留,如果是>=3位那么看第三位是否>0如果是那么保留,否则就忽略仅仅保留两位.
 
$input =~ s/(\.\d\d[1-9]?)\d*/$1/;
那么基本上这个表达式就ok了,不过多少有点美中不足,就是如果我输入0.625/0.62 , 这种情况下替换照样会发生,尽管实际上这种情况不需要进行替换. 也就是说能够做点修改,让这种本来就正确的输入不要执行替换了.
$input =~ s/(\.\d\d(?>[1-9]?)\d*/$1/; 这个表达式0.62还是会发生替换.
再作一次修改
$input =~ s/(\.\d\d(?>[1-9]?)\d+/$1/; 这样就行了. 0.62不会被匹配, 0.625 也不会匹配.
 
这儿看一个关于固化分组的小例子. (?> .*?) 匹配了什么?
我们知道固化分组会将属于自己分组内的保留状态丢弃,另外*?本省表示忽略有限,也就是说先以忽略作为匹配,然后将保留一个..*?这样一个状态,但是这个状态由于在固化分组内,因此被丢弃了,总的来说这个表达式什么都不匹配,写和没有写一样.
 
下面一个例子演示了如何匹配makefile 中类似于src = 1.c  or src = 1.c \
2.c

分为一种单行模式,一种多行模式. 下面我自己想出来的一种匹配模式.

undef $/;
my $input = <>;
my $regex;
$regex = qr/
                ((?:(?!\\\n).)*)(?:\n | )
              /x;
              #((?:(?!\\\n).)*)(\n|\\\n?{{$regex})

while($input =~ m/$regex/xgc)
{
        print "1=".$1."\n";
}

 

下面一个例子展示如何匹配microsoft way的csv 文件,比如excel 生成的.

见附件cvs,rar

文件: csv.rar
大小: 0KB
下载: 下载

例子中如果是,, 这样的空字段也抽去了,如果需要忽略可将*变为+.
 
在记录到了今天2008-1-1 ,master regular expression 3rd 的前5章已经读完一遍.
下面进入第六章,也就是提高效率.
 
下面的时学习心得,分析了 (B | X)* , (B* | X)* , (B* | X) 之间的区别.
这个例子演示了将所有输入文本中括号内的内容print出来,不管嵌套多少层次.
// 下面这个例子中要特别注意,一定要先定义 my $regex; 否则直接声明和定义写道一块儿就会出现
// 异常效果.
my $regex;
 $regex = qr/
                \(
                        ((?: [^()] | (??{$regex}))*)
                \)
          /x;
dodo($input);
sub dodo
{
        print "begin ---- \n";
        my ($input) = @_;
        while($input =~ m/$regex/g)
        {
                my $index = 1;
                while(defined $$index)
                {
                        print $$index."\n";
                        dodo($$index);
                        ++$index;
                }
        }
        print "end ---- \n";
}

下面这个例子请你回答一个问题
$input = "11";
if($input =~ m/(.*){2}/)
{
    print $1;
}
你认为答案是多少呢?
我认为从结果来看,保留的备用状态, 在遇到量词{2}后并不会发生回溯, 而回到了忽略的哪个备用状态.
也就是(X){2} , 来匹配 X11 , 这种情况下形成了零长度.

这样再看另外一个问题. 匹配(123(4))
1. ((?: [^()]* | (??{$regex})))
2. ((?: [^()]* | (??{$regex}))*)
3. ((?: [^()] | (??{$regex}))*) 
上面三种写法的差别,我们已经知道了3是正确的答案.那么1,2的问题在哪儿?
1. 由于否定字符集的*贪婪量词匹配,  导致回溯的顶层备用状态中存储的是*的备份,比如123匹配后,由于后面是) , 因此会用
  3匹配) , 然后用2,最后用1,然后再用(??{$regex}) , 匹配) , 由于全部无法匹配,因此驱动字符前进到2, 同样的,也不行,
知道碰到内迁的德(4)时候才发生了真正的匹配,因此1,的结果是不同于3的.
2. 这种方式,现在我还是不太清楚真正的内部回溯流程,但是一般来说**双重一般不是一种好的用法.
3. 回溯的时候才会用到保留的(B | .X) * ,位于X前的回溯流程,而导致匹配.
 
重要: 在观察回溯的时候, $regex = qr/  (?{print "start matching at [$`|$'] \n"})
                \(
                        ((?: [^()] | (??{$regex}))*)
                \)
          /x;
第一行的内嵌perl代码能够暂时内部驱动过程.
下面的代码print除了驱动过程.
////////////////
C:\testperl>perl parentheses.pl
(1(2))
begin ----
start matching at [|(1(2))]
start matching at [|(2))]
start matching at [|))]
start matching at [|)]
1(2)
begin ----
start matching at [1|(2)]
start matching at [1|)]
2
begin ----
end ----
end ----
end ----
/////////////////////
 
举个例子用上面3的表达式来匹配 (1(2)).
在画回溯图时候需要注意,如果是碰到 (B | X) * 之前,将产生一个忽略*的备用状态.
在进入括号后将产生一个 .X (B | X) * 的状态. 在B发生匹配后将产生 B . (B | X) * , 这样一个匹配中间过程,这个时候再驱动前的时候将产生 B (B | x) * . \) 这样一个被用状态. 驱动进入括号后产生 B . X (B | X)* 这样的中间过程.  .  表示目前在表达式中的处理位置.
这样来理解表达式3就基本能够明白.
 
另外用类似的方式也能分析出1,2的运行模式,记住一点,碰到*覆盖的括号,在驱动过程中,那么就有一个忽略此()的状态产生.
 
//////////////////////////////////////////////
 
通过对内迁括号的例子后,再看一个多字符的例子,查找 之间的内容. 这个貌似和括号一样.
1. 这个涉及到时多字符 有多个字符组成的guard.
2. 也可能有嵌套.
 
我们还是从简单的开始来.
m/.*?/i
这个解决方法能够解决没有内嵌的.
如果碰到 fake real 之类的,匹配的结果就是不对了.
 
m/ ((?!).)*? /i , 这样就可以匹配 real 了.
或者也可以写成 m/ ((?!).)* /i , 也是类似.
 
环视内部的回溯就像一般的表达式一样,不过环视内部保留的备用状态一旦出了环视就消失了.
而环视这种特性就可以在不支持固化分组的时候使用环视模拟.
(?> regex) ===  (?= (regex)\1)
 
在perl这个工具中多选结构是顺序优先的.
 
下面来看一个分解makefile的例子.
下面这个例子读取一个makefile文件,同时分解出所有的 xxx = xxfxc  格式,且允许以\ 换行,行内\表示目录.
 
undef $/;
my $input = <>;
my $regex;
$regex = qr/\G
                ((?: [^\\\n] | *.)*)
              /xs;
              #((?:(?!\\\n).)*)(?:\n | )
while(not $input =~ m/\G\z/)
{
        if($input =~ m/\G\s*\n/gc)
        {
        }
        elsif($input =~ m/$regex/gc)
        {
                print "1=".$1."\n";
        }
}
 
 
 
在处理rgs文件的大括号匹配时候发生的匹配
my $cnt=0;
my $regex = qr#
                (?
                    (
 (?{print "start matching at [$`|$'] $cnt\n",++$cnt})
                        \{
                            (?: (?>[^{}]*) | (?2) )*
                        \}
                    )
                )
                #x;
my $file=$ARGV[0];
my $fh;
open($fh, "<", $file) or die "open $file error!";
undef $/;  # enter file slurp mode
my $input = <>;
$input = decode("cp936",$input);
if($input =~ m/$regex/si)
{
    print "match".$+{content}."\n";
}
 
这儿非常需要注意(?: (?>[^{}]*) | (?2) )* / (?: [^{}]* | (?2) )* 这个的写法,如果是一个合规的输入那么运行起来效果是不错的,但是如果给一个非法的输入,比如括号不匹配那么前后两个写法的运行次数差别一个是20次,一个是17w次。
合法输入
{
 N
 {
  N
  {
  }
 }
}
 
非法输入
{
 N
 {
  N
  {
  }
 }
 
阅读(780) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~