关于网上流传查找PHP webshell的python脚本中,不严谨的代码

不是闲着蛋疼,也不是批评谁,只是不忍心看到不严谨的代码在网上被疯传,误导初学者。以下引用的代码以及思路来自网络,只针对代码,不针对人。如有雷同,纯属巧合。

来自某大牛python版本的检测服务器上PHP代码中webshell的脚本代码片段如下:

findtype=['.php','.inc'] #要检查的文件后缀类型
keywords=[ ["eval\(\$\_POST","发现PHP一句话木马!"], ["(system|shell_exec|exec|popen)","发现PHP命令执行函数!"]]

然后,接着是python处理上面列表(数组),把每个列表的第一个元素作为正则表达式内容,然后进行匹配处理的。先看第一个检测“PHP一句话木马”的这个正则。【eval\(\$\_POST】这里转义了字符【_】,字符“_”不是元字符,不需要转义的,其实,转义也无妨,聪明的正则表达式引擎会识别修正的。来说下这个思路的遗漏之处。这里仅仅匹配字符串“eval(”后面的变量“$_POST”,如果是“$_GET”呢?如果是“$_REQUEST”呢?如果程序先把$_POST/$_GET/$REQUEST的key的变量先赋值给一个自定义的变量名,然后在用PHP的“eval”函数执行这个变量呢?是不是就查找不到了?其实,并不需要知道“eval”函数执行的变量名是什么,只要查找“eval(”即可,当然,还要注意“eval”函数后面可以有空格,tab键之类空白字符。上面的代码片段中,也没考虑到这一点。

某检测PHP webshell的python脚本考虑欠佳。

再看看下一个列表的第一个元素。【(system|shell_exec|exec|popen)】,这个正则的意思是只要字符串里包含“system”、“shell_exec”、“exec”、“popen”这四组字符串即判定为危险字符。很明显,这个方法太不严谨。如果程序员写的代码中,包含了这四组字符,即可被判定为危险函数。很不准确,误报率极高。见下图

某检测PHP webshell的python脚本考虑欠佳。

到底什么样的代码是可疑的代码?关键词是什么?

可疑的代码肯定是由可以执行危险操作的函数构成,可以执行危险操作的PHP函数最重要的就是“eval”函数了,对于加密的PHP代码(仅变形字符串,非zend等方式加密),肯定要用到“eval”函数,所以,对于不管是用哪种加密方法的代码,肯定要用到“eval”函数。其次就是可以执行系统命令的函数了,比如上面某牛的代码中提到的四个“system”、“shell_exec”、“exec”、“popen”。当然还有其他的,比如passthru等。PHP还支持“·”字符(ESC键下面那个)直接执行系统命令。我们可以把正则写成这样【\b(?P<function>eval|proc_open|popen|shell_exec|exec|passthru|system)\b\s*\(】。

检测PHP webshell的python脚本相对较为严谨的匹配

解释一下:

大家都知道【\b\b】用来匹配单词两边的位置的。要保证【\b\b】中间的是单词,即使函数名前面加特殊字符,也一样通过匹配,比如加@来屏蔽错误。后面的【\s*】用来匹配空白字符的,包括空格,tab键,次数为0到无数次。前面的【(?P)】是捕获命名组。用来当作python代码直接引用匹配结果的key。

还有的网友提到了,如果我把代码放到图片拓展名的文件里呢?那你只检测.php,.inc的文件,还是找不到我的呀。嗯,是的,如果恶意代码在gif、jpg、png、aaa等乱七八糟的拓展名文件里,是不能被apache、IIS等web Services解析的,必须通过include/require(_once)来引入。那么,我们只要匹配include/require(_once)后面的文件名是不是常规的“.php”、“.inc”文件。如果不是,则为可疑文件。正则如下【(?P<function>\b(?:include|require)(?:_once)?\b)\s*\(?\s*["'](?P<filename>.*?(?<!\.(?:php|inc)))["']】。

检测PHP WEBSHELL的python脚本较为严谨做法

解释一下:

先看【(?P<function>\b(?:include|require)(?:_once)?\b)】,【(?P<name>)】为正则表达式的“命名捕获”,PHP中有同样的用法。也就是说,在这括号内的捕获的数据,会分配到结果数组的key为“name”的value中。再看里面的【\b(?:include|require)(?:_once)?\b】,【\b\b】不解释了,为单词边界位置。里面的【(?:include|require)】匹配字符串“include”、“require”两个单词,其中前面的【(?:)】未不分配组,用于提高效率,可以去掉【?:】变成【(include|require)】。在后面一个【(?:_once)】也是做不分配组的操作,便于提高正则表达式效率。同样,后面的量词是“?”代表这个组可有可无。就满足了“include”、“include_once”、“require”、“require_once”四种情况。有的朋友可能这样写【(include|include_once|require|require_once)】也能实现目的。但是,为了更搞的效率,我们对这个正则做优化,针对部分字符串做分支更改,改成上面那个【\b(?:include|require)(?:_once)?\b】。

再看下面的【\s*\(?\s*["'](?P<filename>.+?(?<!\.(?:php|inc)))["']】中,【\s*】匹配空白字符,包括空格,tab键等。后面的【\(?】,匹配字符“(”,后面的量词“?”表示这半个小酷括号可有可无。防止“incude “123.php””这种没有括号的情况。再后面【["']】匹配双引号,单引号的。最后的也是。再看看这个【(?P<filename>.+?(?<!\.(?:php|inc)))】,其中【(?P<filename>)】上面介绍了,为命名捕获,把结果放到match.group(“filename”)里。【.*?】为任意字符,后面的量词是“忽略优先量词”,也就是平常说的“非贪婪”。这里最少匹配零个,(防止.aa、.htaccess这种没有文件名,只有文件拓展名的文件被引入)。后面的【(?<!\.(?:php|inc))】,这里用到了反向零宽断言(环视)的非操作(只匹配位置,不匹配字符串,跟【^$\b】等一样)。这个表达式是针对这个位置的后面字符起作用的,也就是说后面的【["']】的前面不能是“.php”、“.inc”,这里也就是取了文件名的最后的拓展名。(正则里,可以用【^】对字符取非,但是不能对“字符串组”取非,这里用了零宽断言来实现。)

综上所述,最后,鄙人给出的python代码如下:

#!/usr/bin/python
#-*- encoding:UTF-8 -*-
###
## @package
##
## @author      CFC4N   <cfc4nphp@gmail.com>
## @copyright   copyright (c) Www.cnxct.Com
## @Version     $Id: check_php_shell.py 37 2010-07-22 09:56:28Z cfc4n $
###
import os
import sys
import re
import time
def listdir(dirs,liston='0'):
	flog = open(os.getcwd()+"/check_php_shell.log","a+")
	if not os.path.isdir(dirs):
		print "directory %s is not exist"% (dirs)
		return
	lists = os.listdir(dirs)
	for list in lists:
		filepath = os.path.join(dirs,list)
		if os.path.isdir(filepath):
			if liston == '1':
				listdir(filepath,'1')
		elif os.path.isfile(filepath):
			filename = os.path.basename(filepath)
			if re.search(r"\.(?:php|inc|html?)$", filename, re.IGNORECASE):
				i = 0
				iname = 0
				f = open(filepath)
				while f:
					file_contents = f.readline()
					if not file_contents:
						break
					i += 1
					match = re.search(r'''(?P<function>\b(?:include|require)(?:_once)?\b)\s*\(?\s*["'](?P<filename>.*?(?<!\.(?:php|inc)))["']''', file_contents, re.IGNORECASE| re.MULTILINE)
					if match:
						function = match.group("function")
						filename = match.group("filename")
						if iname == 0:
							info = '\n[%s] :\n'% (filepath)
						else:
							info = ''
						info += '\t|-- [%s] - [%s]  line [%d] \n'% (function,filename,i)
						flog.write(info)
						print info
						iname += 1
					match = re.search(r'\b(?P<function>eval|proc_open|popen|shell_exec|exec|passthru|system)\b\s*\(', file_contents, re.IGNORECASE| re.MULTILINE)
					if match:
						function = match.group("function")
						if iname == 0:
							info = '\n[%s] :\n'% (filepath)
						else:
							info = ''
						info += '\t|-- [%s]  line [%d] \n'% (function,i)
						flog.write(info)
						print info
						iname += 1
				f.close()
	flog.close()
if '__main__' == __name__:
	argvnum = len(sys.argv)
	liston = '0'
	if argvnum == 1:
		action = os.path.basename(sys.argv[0])
		print "Command is like:\n	%s D:\wwwroot\ \n	%s D:\wwwroot\ 1	-- recurse subfolders"% (action,action)
		quit()
	elif argvnum == 2:
		path = os.path.realpath(sys.argv[1])
		listdir(path,liston)
	else:
		liston = sys.argv[2]
		path = os.path.realpath(sys.argv[1])
		listdir(path,liston)
	flog = open(os.getcwd()+"/check_php_shell.log","a+")
	ISOTIMEFORMAT='%Y-%m-%d %X'
	now_time = time.strftime(ISOTIMEFORMAT,time.localtime())
	flog.write("\n----------------------%s checked ---------------------\n"% (now_time))
	flog.close()
## 最新代码在文章结尾的链接里给出了。2010/07/31 更新。

仅供参考,欢迎斧正。

下面截图为扫描Discuz7.2的效果图,当然,也有误报。相对网上流传的python脚本,误报更少,更精确了。

检测PHP WEBSHELL的python脚本的检测结果

问:这个方法完美了吗?可以查找目前已知的所有危险函数文件了吗?
答:不能,如果include等引入的文件没有拓展名,这里就匹配不到了。
问:如何解决?
答:留给你解决,聪明的你,肯定可以搞定。
PS:“`”反引号 执行命令的还没写,暂时没好的办法。容易跟SQL语句中的反引号混淆。不太好匹配。如果光匹配反引号就提示的话,那误报太大了。待定吧。(术业有专攻,请勿因为一处不好的代码,否定一个人的能力。你懂的。再次重申,此文只针对代码,不针对人。其次,鄙人给出的python代码随便复制,随便传播,爱留版权就留版权,不爱留就删了相关字符,也就是您爱干吗干吗。)
我先休息一会,明天再说。(前半句为三国杀曹仁的台词,哈。)

=============================我是万恶的分割线======================================
最新代码在这里给出了。

snoopy.class.php中_striplink方法的正则分析

上周提到老爷机坏了,昨天早上早早的抱去修理,维修工程师更换硬件排除法最后得知,故障出现在显卡,插入显卡,主板灯亮,按开关,没反映,或者CPU风扇抖动一下即停止。卸下显卡,开机正常。这位维修人员的结论是显卡坏了,建议更换显卡。我们有N卡9200,只要320元。我提出把显卡放到别的主机上试试,被拒绝了,好吧,那我也拒绝。最后,对方收我20元检测费。虽然我心里有点不情愿,他只是换了几个零件的简单排除法得出很有可能的结论而已,并没有告诉我准确的病因,是显卡的哪里出问题了?能否维修?他都没有告诉我。尽管这样,我还是给了,也没争论。

主机抱回来之后,自己卸下了显卡。开机,上网,打算到网上买块新的。老的显卡是我07年在学校买的。ATI RADEON X550,在当初,还算比较前卫的硬件。周五还赞扬其能勤奋工作到现在,而结果得知出问题的就是它。工作了3年多,不到四年,还能接收吧。主板换了华硕P5GC-MX/1333 ,作为硬件小白的我,也不知道换什么显卡较好。熟悉硬件的朋友,麻烦留言告诉一下,感谢了。现在打开网页,拉动浏览器滑动条的时候,卡的比较厉害,播放电影更是,像放映幻灯片一样。实在不能接受。遂网上搜了相关信息。价格在500左右。是有点贵了。遂暂时作罢。一整天心情都很低落,或者说,自从老爷机出问题了,心情一直很差。中午窝在家里,洗了洗衣服,趁太阳好,把冬天穿的衣服拿出来晒了晒。又挪动电脑附近的两个大桌子,打扫卫生,清理一番。一直忙到下午3点,头饿的晕忽忽的。匆忙到沙县小吃随便吃了点,下午,看会书,睡了会觉,早上送GF回家,起得太早了,困的厉害。睡醒之后,都7点多了,照照镜子,看看自己蓬松的发型,猥琐的样子,不禁笑了一笑。洗了把脸,到小区的理发店里剪了头发,由于是小区内,专门给老头老太太理发的,所以费用仅5元,在上海,应该没有比这还低了吧。晚上,把显示器接到电视盒上,看了会电视,差不多都是娱乐类的弱智脑残节目,我极为反感觉,又将显示器接回主机箱,开机到当当网上看了一番。因工作需要,要做个代码自动审计化的程序,我的想法是,不仅仅停留在对关键危险函数的正则匹配,而是做到语法分析,识别未初始化的变量、未过滤的富文本字段、SQL注入的方法等。想必,肯定的熟悉《编译原理》以及LEX、YACC之类相关知识,后悔大学没认真听,造成自己基础薄太弱了。当然,我也觉得大学的时候,直接教学生这么底层的知识没有循序渐进的引导学生去深入更好。可以从最简单的网页制作开始,然后,让学生对其感兴趣,再引导到动态,然后,联合数据库,然后效率优化,然后代码执行原理,然后编译原理类似这种步骤引导学生更好。比起现在的方式,可能更让学生感兴趣。(扯远了)东看西看,忙到了12点多才睡。

对于已经习惯8点左右起床上班的我来说,生物钟在早上8点左右,准时醒,有时候想赖床,但也无法再次入睡。起床洗漱,打开电脑,玩了两局web的三国杀,逛逛论坛,觉得饿了,才去煮点面条吃。回来继续,无意中打开PPC,看到一位网友“落叶人生”同学的帖子问一个正则问题,纠结了一下是否解释一下,以便给对方释疑解惑,也加固自己的知识。犹豫半天,觉得还是写下吧。下面正题:

PPC链接http://bbs.phpchina.com/thread-189797-1-1.html

  1. preg_match_all(“‘<\s*a\s.*?href\s*=\s*([\"\'])?(?(1)(.*?)\\1|([^\s\>]+))[^>]*>?(.*?)</a>’isx”,$document,$links);

对于一般的规范链 接能很好的使用,但不知道为什么对href=后面的网址不含引号的情况无法提取成功,哪个朋友帮分析下?谢谢!

其实,我没明白他的意思是想提取链接地址href后面的内容的,还是提取整个标签a中间的所有。

在下面的回帖中,他提到了这个正则来自snoopy.class.php的_striplink方法中的正则,遂到sf.net上下载一份原版到本地。

代码如下

	function _striplinks($document)
	{
		preg_match_all("'<\s*a\s.*?href\s*=\s*			# find <a href=
						([\"\'])?					# find single or double quote
						(?(1) (.*?)\\1 | ([^\s\>]+))		# if quote found, match up to next matching
													# quote, otherwise match up to next space
						'isx",$document,$links);

		// catenate the non-empty matches from the conditional subpattern

		while(list($key,$val) = each($links[2]))
		{
			if(!empty($val))
				$match[] = $val;
		}

		while(list($key,$val) = each($links[3]))
		{
			if(!empty($val))
				$match[] = $val;
		}

		// return the links
		return $match;
	}

先看下修饰符部分。【isx】,大家都知道【i】是不区分大小写;【s】是点号通配模式,也就是元字符【.】可以匹配换行符。在这里的作用是为了防止链接中出现换行的情况;
【x】宽松排列和注释模式,也就是空白字符不会被当成正则表达式的一部分,#后面的为注释,方便阅读的作用了。顺便说下括号后面的双引号后面的单引号,跟修饰符i前面的单引号,他们只是正则的起始标识,不是正则的一部分。其实,表达式里有单引号了,这里最好使用别的,比如斜杠,或者闭合的大括号等。
再看表达式【<\s*a\s.*?href\s*=\s*】,其后面也注释了,是匹配“<a href=”的,第一个【\s】匹配”<”到”a”之间的空白字符,不过一般都没的吧。都直接写”<a……”。后面的【\s】也是空白字符,不详细解释了。在后面的【.*?】是匹配“<a “到“href=”之间的内容,比如”title=”…..”、target=”….”等。【href=】中没有元字符,就是匹配【href=】.

再后面【([\"\'])?】这里的单引号,双引号是被PHP语法转义的,真正的表达式是【(["'])?】,匹配单引号和双音号,并且,用小括号分配了组(整个表达式的第一组),后面有【?】量词,意味这可有可无。

再后面很重要了。去掉PHP的转义字符之后【(?(1) (.*?)\1 | ([^\s>]+)】,这是一个正则表达式的高级用法,叫“条件判断”表达式。语法为【(? if then| else)】。这里的if部分为【(1)】也就是前面说的“整个表达式的第一组”的内容,如果为真,则使用表达式【(.*?)\1】来匹配(\1是引用捕获的第一个组的内容),如果【(1)】没匹配到,则使用【([^\s>]+)】来匹配。这样,就可以实现没有引号的情况下,也是可以匹配到了。(正则表达式的条件判断语法,【|else】可以不写,表示不用else部分的表达式)

对于楼主的问题。应该怎么改?我刚刚刷新了帖子,看到楼主的正则是可以匹配的呀。

汗,贴下PHP的代码吧。


if (preg_match('%<\s*a\s.*?href\s*=\s*(["\'])?(?(1)(.*?)\1|([^\s>]+))[^>]*>(.*?)</a>%si', $subject)) {
	# Successful match
} else {
	# Match attempt failed
}

文章标题跟内容有很大出入,见谅哈。

周末记事-还是我的老爷机

我的老爷机:
06年买的主机箱+飞利浦CRT显示器,07年更换电源风扇,08年上半年分别更换了主板,硬盘,下面年,在内存最低价的时候,99块包快递买了DDR2 667的内存条一根,更新了老内存。09年再次更换了电源风扇,10年由于换地方住,搬家,桌子太靠墙,CRT大屁股显示器放不下,买了优派的22寸液晶显示器,鸟枪换炮了,哈。CRT显示器忍痛割爱以50元的价格卖给了小贩,哎,心痛。接着,鼠标又不灵了,更换了鼠标。从06年坚守到现在的器件只有CPU、主机箱、显卡,光驱、键盘了。

昨天晚上,10点半左右,老爷机又突然间罢工了,顿时,我心急如焚呀,心想即将到来的周末,作为宅男,我不能接受。赶紧调试吧。首先,我以为是天气太热,导致CPU温度过高,激活主板自动保护策略,强制关机的。赶紧把CPU风扇拿出来,放到空调边吹了会,装上去之后,开机,还是不行,主板灯亮了,但是CPU风扇不转,而且,电源也没反映,我愕然了,难道电源又坏了不成?好吧,我认输,周末再说吧,睡觉。

前序:
在老爷机罢工之前,一群内网友求助正则匹配html标签中 table之间的内容,要发现的第一个table之间的内容。字符串大约为

<table class="t1 t2"><tr><td><table class="t1 t2">sssssssssssssssss</table></td></tr><tr>
<td><table class="t1 t2">ss
sssssssssssssss</table></td></tr></table>

同学A提供【<table[\s\S]*</table>】(具体记不清了,大体是这个)

这个正则的匹配结果是不能满足其要求的,很明显,【*】为量词优先,导致贪婪匹配把后面最后一个</table>之前的字符串全部匹配进去了。

见截图:

A同学提供的不严谨的正则表达式

接着,另外以为网友提到,后面加U修饰符。对于这个正则,对于这个要求,加U修饰符能达到目的吗? 答案是可以的。为什么?这种方式可靠吗?有风险吗?有其他方法吗?

先来解释一下U(大写字母U,非小写,小写的是uincode字符模式)是干吗的? 是对“匹配优先量词(贪婪)”与“忽略优先量词(非贪婪)”的取反,也就是说,原来是【*】、【+】的,会按照【*?】、【+?】来执行,如果是【*?】、【+?】的,则按照【*】、【+】来执行,这个修饰符很怪,很乱,如果正则为【\w+】,为了达到“非贪婪匹配”,使用了修饰符U。过几天,你修改了这个正则,修改为【\w+\s+?】,你的本意是在原来的基础上,加上非贪婪的空白字符的匹配,可是,后面的大写字母U修饰符也对你前面的非贪婪取反了,从而,导致匹配错误,所以,建议大家不要使用大写字母的U修饰符,直接在表达式里写好贪婪还是非贪婪。

我的答案是【<table[^>]+?>(?>((?!</table>).(?<!<table))+)</table>】。

我靠,你这这什么玩意?这么乱?这是什么意思?

解释一下:

  1. 【<table[^>]+?>】部分是匹配“<table class=”t1 t2″>”这种table的开头,包括属性的字符串,其中,使用了忽略优先量词(非贪婪),由于其属性不会太多,常规下不超过50个字符串,所以,没有使用固化分组,或者占有优先量词来减少回溯。
  2. 【</table>】(最后面的)是匹配字符串结束的。不用解释了吧。
  3. 【(?>((?!</table>).(?<!<table))+)】这个比较长,主要是匹配<table….到</table>中间的字符串,其中最外面一层【(?>)】是占有优先量词,用来防止不必要的回溯,防止中间超长的字符串造成崩溃。【(?!</table>)】是顺序零宽断言非操作,匹配位置,针对其后面的规则【.】,意思就是说,规则【.】后面不能是字符串“</table>”,同样正则【(?<!<table】是逆序零宽断言非操作,也匹配位置,针对其前面的规则【.】,意思就是说,规则【.】前面不能是字符串“<table…”,这样,两个零宽断言来确定一个字符串,如果是多个字符串,则加上匹配优先量词【+】,但是这个【+】的位置要注意了,不能直接放在【.】的后面,因为要保证每个【.】匹配的字符串,前后的规则都符合两个零宽断言,则需要放在外面,也就是上面所写的那样。

OK,解释完毕,你明白了没?如果没,那是我的错。请留言,批评,我承认错误。(当使用匹配的时候,为了更准确,建议开启不区分大小写模式,以及,多行匹配模式,即修饰符i和修饰符s,小写的s,不是大写的,大写的S是另外的含义<以后写>。)

见截图:

相对比较严谨,比较准确的正则表达式

测试代码:

$str='111<table class="t1 t2"><tr><td><table class="t1 t2">sssssssssssssssss</table></td></tr><tr>
<td><table class="t1 t2">ss
sssssssssssssss</table></td></tr></table>ssss';
preg_match_all('%<table[^>]+?>(?>((?!</table>).(?<!<table))+)</table>%si', $str, $result, PREG_PATTERN_ORDER);
print_r($result[0]);

我的答案可以解决问题吗?可以。准确吗?还行,不太准。为什么?如果table中间有嵌套,则无法正确匹配。如何修复?正则递归匹配。如果递归?

留给你研究吧。CFC4N小试正则表达式里已经介绍了一个关于递归匹配的例子,你可以参考一下。

后续:

老爷机挂了,我一夜没睡着,早上6点10分就醒了,起来之后,捣鼓两下,也没心情了,已经知道大体的症状。手头没工具,也没法测试,算了,索性洗漱去公司。走的早也好,地铁上没多少人,一路不拥挤,唯一的缺点就是没拿到“时代报”。不过到了公司楼下,有派发的,顺手拿了一份。到座位上,心思还在我心爱的老爷机上,也有重新买台新机器的想法,到DELL官方网站上搜了几下,发现价格奇贵,光主机,都4K-5K,看了一会,我就大小这个念头了,作为寄生在这大城市的P民,也别要求太高了。讲究用吧。哎,打算周末冒热抱出去找维修铺,各位,祝福我吧。

PS:下午,拿了早上没看的报纸去厕所,心想,这次终于不干等了,可以打发时间了。当我到厕所里的时候,我震惊了,我靠,马桶上起码5份报纸,而且,还都不一样。汗。

PHP中正则表达式对UNICODE字符码的匹配

酷暑难耐,又在家宅一整天。天气热或许是借口,尽管不热,我也喜欢宅在家。晚上看新三国83集(插一句,最近世界杯很火,可是我不看世界杯。来鄙视我吧),一直看到10点半,突然觉得肚子痛,赶紧直奔厕所。如厕之后,觉得精神抖擞,容光焕发,年轻了二十多岁,打开QQ,看到N多条消息,其中有条是生命如蓝同学留的。打开一看,是PPC的链接,突然觉得我好久没登陆PPC了,想当年……..算了,看链接吧。

打开链接,看到标题是“请教PHP 一个正则匹配的问题”,又是正则表达式,好吧,看下,谁让俺比较喜欢鼓捣正则呢。下面开始正题。
网友ainiaa的问题是

PHP代码如下

$words = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSRUVWXYZ!@#$%^&*()_+-=[]\\,./{}|<>?'\"你好啊我们";
$otherStr=preg_replace("/[chr(128)-chr(256)]+/is"," ",$words);
echo 'otherStr:',$otherStr;

为什么打印的结果会是:
otherStr: ! #$% & {}| ‘”你好啊我们

麻烦问下其中正则表达式 /[chr(128)-chr(256)]+/is 代表什么意思?
如果/[chr(128)-chr(256)]+/is 指的是ascii码在128到256的字符,为什么a-zA-Z这样的字符也被替换掉了,他们的ascii码是小于127的。
最令人郁闷的是为什么ascii码同在0-127区间”#”,”$”,”%”,”&”, “!”,” {“,”}”,”|”,” ‘”,”确没有被替换掉????
更令人感觉神奇的是 如果把正则表达式修改为”/[chr(128)-chr(256)]+/s”的话,输出的结果就变成了: otherStr: defg ijklmnopq stuvwxyz ! #$% & {}| ‘”你好啊我们
只是把正则表达式中的符号‘i’给去掉,结果缺失这样的。 完全的令我理解不了。
不知各位 有何见解????
另附ascii 码 对照表
(这个ASCII码表的图我就不贴了)

回帖中,有个网友说没解析chr(128)这些,并给出了新的解决方法。首先说下此网友回答的是正确的,先不评论他是否“知其然,且知其所以然”,这位网友没有给出错误的原因。

CFC4N来回答一下这位网友:

PHP的正则的preg_match函数用的是PCRE正则引擎,这位网友的代码中,PCRE引擎处理的正则表达式为【/[chr(128)-chr(256)]+/is】,后面的is是什么呢?
在PHP的正则里,边界字符后面的叫模式修饰符。它会告诉引擎如何解析,处理正则。其中i修饰符表示不区分大小写。s表示“点号通配模式”,用来让正则里的元字符点号【.】可以匹配换行符,这个修饰符仅对点号【.】起作用。在这位网友的问题中,修饰符s并不起作用的。

查找原因:
我们在来分析一下这个网友写的正则表达式【[chr(128)-chr(256)]+】,正则表达式的PCRE引擎是如何解释这个正则的呢?首先,我们要知道,在正则表达式中,中括号【[]】表示字符组,字符组中除了连接符【-】只外,都不是元字符,也就是说,都是普通字符,当然,如果连字符出现在第一个,或者不是标识两个字符之间范围的,都是普通的字符横杠“-”罢了。这里的chr(128)只是标识ASCII码为128(确切的说,ASCII码只是0-127个,128到其他的,应该不叫ASCII码了。),但是在正则里,他仍然代表【c、h、r、(、1、2、8、)】(顿号不是,只是区分易读的)这八个字符罢了。这个正则里的连接字符,是哪些范围呢?很明显,这里的连接字符的范围是【)-c】,“)”ASCII码为0×29,也就是十进制的41;“c”的ASCII码为0×63,也就是十进制的99,那么,他这个连接字符的范围就是ASCII 41(chr(41))到ASCII 99(chr(99))之间的字符。也就是说,这位网友的正则的范围是【[hr)-c(]】,就是chr(41)到chr(99)外加hr这两个字母和前面的“(”。
网友第一次测试的时候,有修饰符i,意思就是说,不区分大小写,那么在chr(41)到chr(99)之间的字符,以及这些字符如果有大小写,则包括他们的大小写都符合匹配。都会被替换成空。其第二次测试的时候,去掉了修饰符i,进行了不区分大小写的匹配,由于其范围只到c,但突然,再除了小写字母的“h”、“r”,所以,测试结果会多出“defgijklmnopqstuvwxyz”。所以,他的结果出现了这些差别。

PHP正则表达式匹配UNICODE字符


网友的表达式等同于如下图所示

PHP正则表达式匹配UNICODE字符

解决办法:
错误的原因找出来了,那么,解决的办法呢?
我们先来看看这位网友的需求,他的需求是将unicode(ASCII只是0-127位的,128之后的,应该叫UNICODE码)的chr(128)到chr(255)之间的字符匹配,替换为空罢了。正则表达式里,对十六进制的字符匹配的表示方式有两种,【\u】和【\x{}】,前者只能表示【\u】后面4位的十六进制数值,而后者【\x{}】则可以表示任意多的十六进制位数(写在大括号中)。
那么,这个正则表达式该如何写????

网友的目的是chr(128)到chr(255),那么就是【[\u0080-\u00FF]】或者【[\x{0080}-\x{00FF}]】。
其目的是匹配下图中的红框内字符

UNICODE字符128到255字符集图


提醒一下,PHP里正则匹配unicode字符时,需要使用u修饰符。
根据网友需求,更改正则之后的PHP代码如下:

$words = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSRUVWXYZ!@#$%^&*()_+-=[]\\,./{}|<>?'\"你好啊我们";
$otherStr=preg_replace("//[\x{0080}-\x{00FF}]+/iu"," ",$words);
echo 'otherStr:',$otherStr;

其运行结果是仍然输出那段字符串,为什么呢?因为哪些字符串都不在chr(128)到chr(255)的范围之内。
(测试时,注意文件编码为UTF-8)
以上为鄙人愚见,欢迎批评指正。

PHP正则表达式的效率:回溯与固化分组

PHP正则表达式的回溯与固化分组


上文中,我们聊到了一点关于PHP中(NFA PCRE)正则表达式匹配优先量词,忽略优先量词的匹配原理了。那么上文留下的问题,您的答案是什么呢?
先来看下问题。

字符串

$str = '<script>123456</script>';

正则表达式为

$strRegex1 = '%<script>.+<\/script>%';
$strRegex2 = '%<script>.+?<\/script>%';
$strRegex3 = '%<script>(?:(?!<\/script>).)+<\/script>%';

这三个正则,分别会造成几次回溯呢??

答案:

$strRegex1 = '%<script>.+<\/script>%';    //9次,记得区别转义符号。
$strRegex2 = '%<script>.+?<\/script>%';  //5次
$strRegex3 = '%<script>(?:(?!<\/script>).)+<\/script>%';  //7次

对于第一种贪婪匹配的匹配规则,回溯的9次是正则【】对字符串“”匹配时,构成的回溯,回溯的次数,恰好是字符串的长度。
第二种非贪婪匹配规则,回溯5次,是正则【.+?】对字符串“123456”匹配时构成的回溯。回溯的次数,为字符串长度减去最小次数。也就是6-1=5次。如果正则表达式为【.*?】那么,回溯次数就是6次了。
第三种正则是零宽断言,或者叫环视。(暂且不说。)
在NFA正则引擎中,回溯是他的灵魂,所以,不管是贪婪,非贪婪,环视等写法中肯定会有回溯的出现的,这个我们无法避免(用词不太准确),但是,我们可以减少回溯的次数,或者保护其中一部分匹配的规则不进行回溯。

对于上篇BLOG上提到的鸟哥谈到一个非贪婪引起的大量回溯问题,大家可以知道,回溯,确实是浪费资源的罪魁祸首,那么,我们能否不让其回溯呢?
答案是肯定的,NFA引擎中,有个概念,叫固化分组。引用一下书上的概念

具体来说,使用「(?>…)」的匹配与正常的匹配并无差别,但是如果匹配进行到此结构之后(也就是,进行到闭括号之后),那么此结构体中的所有备用状态都会被放弃。也就是说,在固化分组匹配结束时,它已经匹配的文本已经固化为一个单元,只能作为整体而保留或放弃。括号内的子表达式中未尝试过的备用状态都不复存在了,所以回溯永远也不能选择其中的状态(至少是,当此结构匹配完成时,“锁定(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);  //匹配结果的group1进行反向引用

很明显,这种写法,对于部分数据格式为123.456的这种格式,白白的处理了一遍,为了提高效率,我们还要对这个正则进行处理。从123.456这个字符串跟其他的比较一下,我们发现,是疑问123.456这个数据后面没数字了,所以,白白处理一遍。那好办,我们对这个正则改造一下,把后面的量词*改成+,这样对于123.45 小数点后面1,2位数字的,不会去白白处理,而且,对三位以上数字的,处理正常。其PHP代码为

$str = preg_replace('\.(\d\d[1-9]?)\d+','\\1',$str);

好了,这个正则真的没问题吗??确定吗?上篇博文,我们了解了匹配原理,那么,我们也分析一下这个正则的匹配过程吧。
字符串"123.456",正则表达式为【\.(\d\d[1-9]?)\d+】,我们来看下
首先(小数点前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);

改成这样的话,那字符串“123.456“是不符合要求,不会被匹配的。那我们就可以实现我们的要求了。

从上面的例子中,知道了固化分组的作用,那么对于鸟哥BLOG上写的那个非贪婪的回溯问题,我们能否也对其改造,使得其不回溯呢?
先看下鸟哥给的答案

/<script>[^<]*<\/script>/is

鸟哥写的很精悍。排除“<”之外的所有字符都符合,而且,中间部分不回溯,效率高。可是,如果中间有字符“<“的话(如下代码)

<script>
if a < b
</script>

那鸟哥的这个正则就不能匹配,就不能实现我们想要的功能了。
那我们可以根据 固化分组、环视(零宽断言)来实现这个要求,最后,CFC4N给出的正则以及PHP代码事例如下

$reg = '%<script>(?>[^<]*)(?>(?!</?script>)<[^<]*)*</script>%is';
$str = str_pad("<script>", 111111, "*");    //字符长度大于PHP回溯限制的100000
$str .= 'if a < b ; if b > c;</script>';    //随便加几个包含 < > 的测试字符
$ret = preg_replace($reg, "OK", $str);
print_r($ret);                              //打印结果 OK,证明匹配正确
var_dump(preg_last_error());                //上一次匹配错误。其输出为 int(0)

嗨,同学,你看明白了吗?

以上为小菜CFC4N的愚文,如有错误,欢迎指出。

小议正则表达式效率:贪婪、非贪婪与回溯

前几天看了鸟哥的BLOG上写的关于正则表达式的回溯与递归的限制时,对贪婪、非贪婪产生的回溯有疑问,遂近段时间,仔细的学习研究了一下,现在把经验心得与大家分享一下。
(我日,这里好像被左侧的图挡着了,换行换行凑字数,换行换行凑字数,换行换行凑字数,换行换行凑字数,换行换行凑字数,换行换行凑字数,换行换行凑字数,换行换行凑字数,换行换行凑字数,换行换行凑字数,换行换行凑字数,换行换行凑字数,换行换行凑字数,换行换行凑字数,换行换行凑字数)
先扫盲一下什么是正则表达式的贪婪,什么是非贪婪?或者说什么是匹配优先量词,什么是忽略优先量词?
好吧,我也不知道概念是什么,来举个例子吧。
某同学想过滤之间的内容,那是这么写正则以及程序的。

$str = preg_replace('%<script>.+?</script>%i','',$str);//非贪婪

看起来,好像没什么问题,其实则不然。若

$str = '<script<script>alert(document.cookie)</script>>alert(document.cookie)</script>';

那么经过上面的程序处理,其结果为

$str = '<script<script>alert(document.cookie)</script>>alert(document.cookie)</script>';
$str = preg_replace('%<script>.+?</script>%i','',$str);//非贪婪
print_r($str);
//$str 输出为 <script>alert(document.cookie)</script>

仍然达不到他想要的效果。上面的就是非贪婪,也有的叫惰性。其标志非贪婪的标识为量数元字符后面加? ,比如 +?、*?、??(比较特殊,以后的BLOG中,我会写到)等。即标识非贪婪,如果不写?就是贪婪。比如

$str = '<script<script>alert(document.cookie)</script>>alert(document.cookie)</script>';
$str = preg_replace('%<script>.+</script>%i','',$str);//非贪婪
print_r($str);
//$str 输出为 <script 只有这些了,好像还是不太合适,哈,您知道如何重写那个正则吗?

以上为贪婪,非贪婪的区别介绍。下面,聊下贪婪、非贪婪引起的回溯问题。先看个小例子。
正则表达式为\w*(\d+),字符串为cfc456n,那么,这个正则匹配的$1是多少??

如果您回答是 456,那么,恭喜你,回答了,其结果不是456,而是6,您知道为什么吗?

CFC4N来解释一下,当正则引擎用正则\w*(\d+)去匹配字符串cfc456n时,会先用\w*去匹配字符串cfc456n,首先,\w*会匹配字符串cfc456n的所有字符,然后再交给\d+去匹配剩下的字符串,而剩下的没了,这时,\w*规则会不情愿的吐出一个字符,给\d+去匹配,同时,在吐出字符之前,记录一个点,这个点,就是用于回溯的点,然后\d+去匹配n,发现并不能匹配成功,会再次要求\w*再吐出一个字符,\w*会先再次记录一个回溯的点,再吐出一个字符。这时,\w* 匹配的结果只有cfc45了,已经吐出6n了,\d+再去匹配6,发现匹配成功,则会通知引擎,匹配成功了,就直接显示出来了。所以,(\d+)的结果是6,而不是456。

当上面的正则表达式改为 \w*?(\d+)(注意,此处为非贪婪),字符串仍然为cfc456n,那么,这时候,正则匹配的$1是多少??
甲同学回答:结果是 456。
嗯,是的,正确,是456,CFC4N弱弱的问下,为什么是456 呢?
我在来解释一下 为什么是456
正则表达式有条规则,是量词优先匹配,所以\w*?会先去匹配字符串cfc456,由于\w*?是非贪婪,正则引擎会用表达式\w+?每次仅匹配一个字符串,然后再将控制权交给后面的\d+去匹配下一个字符,同时,记录一个点,用于在匹配不成功的时候,返回这里,再次匹配,也就是回溯点。由于\w后面是量词是*,*表示0到无数次,所以,首先是0次,也就是\w*?匹配个空,记录回溯点,将控制权交给\d+,\d+去匹配cfc456n的第一个字符c,然后,匹配失败,于是乎,接着讲控制权交给\w*?去匹配cfc456n的c,\w*?匹配c成功,由于是非贪婪,所以,他每次只匹配一个字符,记录回溯点,然后再将控制权交给\d+匹配f,接着,\d+匹配f再失败,再把控制权给\w*?,\w*?再匹配c,记录回溯点(这时\w*?匹配结果是cfc了),再把控制权给\d+,\d+去匹配4,匹配成功,然后,由于量词是+,就是1到无数次,所以,接着往后匹配,再匹配5,成功,再接着,再匹配6,成功,再接着,继续匹配操作,下一个字符是n,匹配失败,这时,\d+会吧控制权交出去。由于\d+后面已经没有正则表达式了,所以,整个正则表达式宣告匹配完成,其结果就是 cfc456, 其中第一组结果是456。亲爱的同学,您明白刚刚的题目的结果,为什么是456了吗?

好了,您是否从上面的例子了解了贪婪,非贪婪的匹配原理了?那您是否明白您在什么时候需要使用贪婪,非贪婪去处理您的字符串了?
鸟哥的文章里讲到针对
表达式、程序为

$reg = "/<script>.*?<\/script>/is";
$str = "<script>********</script>"; //长度大于100014
$ret = preg_repalce($reg, "", $str); //返回NULL

其原因就是回溯太多了,直到造成耗尽栈空间爆栈。

再来看个例子。
字符串

$str = '<script>123456</script>';

正则表达式为

$strRegex1 = '%<script>.+<\/script>%';
$strRegex2 = '%<script>.+?<\/script>%';
$strRegex3 = '%<script>(?:(?!<\/script>).)+<\/script>%';

这三个正则,分别会造成几次回溯呢??

答案见下篇 PHP正则表达式的效率:回溯与固化分组

端午节之后一周总结

本周主题:瞎扯淡。

CFC4N 端午节之后一周总结

记下本周(确切的说,是上周)流水账:

  • 在当当网上买了本书《精通正则表达式》,送货速度很快,前天晚上下的订单,第二天中午就送到了。值得表扬。
  • 姐姐来我这玩,带来了一点粽子,蜜枣馅的,还有煎饼等家乡的特产,非常感谢。
  • 陪小外甥再次看了《汽车总动员》,!@#¥#%%¥……¥&%……*……
  • CNXCT.COM域名已经到期,忘记续费了,还好,GLEON同学没忘记,悄悄的续费了。
  • 端午在家看了新买的书《精通正则表达式》,直接从第四章匹配原理开始看,因为我买书的主要原因是想知道匹配原理。还有就是仔细的看了在非贪婪、贪婪的情况下,匹配时候的回溯详情,匹配的优先顺序,回溯的次数,效率等情况。致力做到不光写得出正则,还要写的出高效率的正则表达式
  • 最近比较懒,从过年回来,基本没运动过,上上上周,打了羽毛球。总共运动时间大约20分钟,其中,大部分时间是捡球,羽毛球老是被我抽到树枝上。想办法把羽毛球取下来,也是件麻烦事。周围没有很长的竹竿,树枝之类东西,只好从地上捡起石块砸。结果,右手胳膊酸了一周。昨天,跟几个朋友一起打升级。由于右手一直轻轻的拿鼠标,没有那么用力的捏牌,结果,今天胳膊又酸了。CFC4N想弱弱的问,难道打升级也算运动?
  • 昨天,把床上铺的床单取掉了,直接躺在竹席上,结果,夜里肚子被冰的很痛,一夜没睡好,结果,现在精神状态很差。
  • 今天看到还算是比较轰动的IT新闻,马化腾马云收编草根网站 互联网大佬争夺站长,新闻的主要内容是腾讯收购草根的IT公司康盛创想,其主打产品是Discuz!,不知道是真还是假。很多网友都调侃说到“期待使用’QQ PassPort‘登陆各大BBS”,我稍微想了一下,如果实现,应该也不难。CFC4N想了一个方案,各位网友来批评指正一下。 腾讯可以做一个系统,各大论坛使用者提交论坛的主域名,腾讯再根据论坛使用者的主域名分配一个私钥,然后,论坛使用者将此私钥更改到论坛程序的认证部分文件中。当用户在其中的论坛用QQ登陆的时候,直接跳转到腾讯的登陆页,登陆成功之后,返回到用户登陆前的论坛,论坛再根据私钥来解密cookie,来判断此cookie是否生效。
  • 无意中发现google对我们的BLOG www.cnxct.com 的快照更新了,而某度却还是那样。详情见下图

google更新cnxct.com 的快照截图


BAIDU未更新cnxct.com 的快照截图

呃。差点忘了,刚刚从QQ网络硬盘里,找出N年前收藏的工具SPXInstantScreenCapture,试用截图了,效果就是上图。其中,百度截图那张,有水印。原因是没注册,其实我有别人的注册码,只是我没用,打算中午用OD自己破解一下,很久不搞,基本的反汇编工具都生疏了。温习一下。

coolcode转SyntaxHighlighter与Mysql正则表达式

blog的代码高亮插件原来是coolcode的,coolcode的高亮插件确实很酷,显示效果也很棒,但是占用的位子太大了。最近,我抽空改成SyntaxHighlighter。由于coolcode插件的开头标签是

<coolcode>

或者[coolcode]这样的,而SyntaxHighlighter是

[code lang="php"]

这样的(或者其他)。遂只能想办法把老的格式转化成新的格式。当然,肯定用到正则表达式了。

原来的代码高亮开头标识为

<coolcode lang="php" download="123.php" linenum="on"><coolcode lang="php" linenum="off"><coolcode lang="php">

这种类型的,
而SyntaxHighlighter的标识为

[code lang="php"]

那根据要求写的正则表达式为

<coolcode lang="[a-z]+".*?>

解释一下

[a-z]+ 匹配 php,javascript,cpp,sql,css 等,后面的.*?中的 .表示任何除了换行之外的字符,而*表示0次或者无数次,*+这些表述次数的符号后面接的?标识非贪婪模式

简单的正则表达式匹配字符串


看图,这个正则可以实现上述要求了。

但是,问题还没解决,我们还有一种情况没考虑,那就是

<coolcode

后面不一定接的就是lang="php"这样的属性啊,有可能是download,也有可能是linenum="on/off"啊,所以,我们的正则还需要改。
CFC4N把正则改为

<coolcode.*?lang="[a-z]+".*?>

截图如下

正则表达式非贪婪匹配


细心的朋友可能看出来图中匹配的红色框内多出了
<coolcode

,意思也就是说,前面的

<coolcode>

需要排除掉。如何排除呢?聪明的你肯定立刻想到.这个万能字符替换成非<>两个符号的规则,好,CFC4N立刻修改一下。
修改之后的正则为

<coolcode.*?lang="[a-z]+".*?>

果然,匹配正常了。结果见截图。

mysql与正则表达式

到这里,问题似乎解决了,可是,当初糊涂的我,把coolcode的两种开头标识都用了,那就是

<coolcode

和[coolcode,那么,看官您认为这个正则该如何改写呢?
没错,无非就是开头,结尾的标识考虑两种情况<和[,那么正则就好改了。(别忘了排除规则里的符号哦)

[<\[]coolcode[^<>\[\]]*?lang="[a-z]+"[^<>\[\]]*?[>\]]

嗯,好,我们来看下效果:

PCRE引擎php与正则表达式


很好很完美。
下面,就可以去执行了。
可是,我遇到一个很意外的事情。居然发现老的代码里包含这样的格式

[coolcode linenum=\"off\" lang=\"cpp\"]<coolcode download=\"\" lang=\"cpp\" linenum="off">

呃,问题在这里了,只是多了个转义字符\罢了,那么,改起来,也简单。也就是允许\出现0次或者一次,而标识0次或者1次的符号为?,那么我们直接在\后面加个?,也就是改成这样\?就可以了吗?
显然,不是。在正则表达式里,\也表示转义,那么,匹配\的话,也得转义一下\,则应该为\\? 这样才对。
修改后正则为

[<\[]coolcode[^<>\[\]]*?lang=\\?"[a-z]+\\?"[^<>\[\]]*?[>\]]

匹配结果见下图:

正则表达式的斜杠转义

现在,大功告成了。我们可以进行转换了。关于转换,我们可以用两种方法。

  • Mysql的REPLACE函数,单个的去替换
    <coolcode lang="php/cpp/javascript/sql/css等" download="name" linenum="on/off">

    为对应的

    [code lang="php/cpp/javascript/sql/css等"]

    ,这样操作,省的去写程序,取出,替换,再写入了,缺点是量大,手工也挺累,体力活。mysql仅仅支持正则查询,不支持正则查询的替换,我们也可以构造联合嵌套的SQL来替换正则匹配的字符串,但是无法取出php/cpp/javascrip这样的语言标记,替换为新的语言标记。也就是说,mysql不支持正则表达式的反向引用。

  • PHP读数据库,替换,再写入。PHP的preg_replace函数支持反向引用(preg_replace不支持自定义组名的反向引用),我们只好写个查询语句,查询包含coolcode标识的文章,然后再替换,当然,直接查询包含coolcode的文章可能太多,我们也可以写个MYSQL支持的POSIX正则引擎的表达式,来匹配使用coolcode标签的文章,再来替换,写入。以减少文章的操作量。当然正则表达式也会浪费很大的资源。

当然,在PHP代码的preg_replace函数使用上面的正则,进行反向引用时,需要对正则稍作修改。给lang=""中间的一个组名。正则修改为

[<\[]coolcode[^<>\[\]]*?lang=\\?"([a-z]+\\?)"[^<>\[\]]*?[>\]]

PHP的替换代码为

$contents = preg_replace('/[<|[]coolcode[^>[\]]*?lang=\\\\?"([^"]+?)\\\\?"[^>[\]]*?[>|\]]/i','[code lang="\\1"',$contents);

其中正则的i修饰符标识不区分大小写。

preg_replace的反向引用


还有,别忘记了coolcode的结束标识和[/coolcode]要替换成[/code]。
mysql里执行两句sql即可

UPDATE wp_posts SET post_content = REPLACE(post_content,'</coolcode>','[\/code]');  //注意后面多了个反斜杠,记得去掉
UPDATE wp_posts SET post_content = REPLACE(post_content,'[/coolcode]','[\/code]');  //注意后面多了个反斜杠,记得去掉

总结:
本文牵扯的正则表达式并无高级用法,都是平常很简单的用法。关于PCRE引擎正则表达式的递归(迭代),组命名,反向引用,零宽断言等,CFC4N会在以后的时间里,找合适的例子写出来。当然,这些高级用法,CFC4N在帮朋友写的正则表达式里已经用到了,大家可以看看,欢迎批评和指点。
PS:如果需要coolcode转SyntaxHighlighter的完整PHP程序,留言即可,我抽空写出来。

关于正则表达式的贪婪与非贪婪模式(转)

这个是转载的,放这里,是方便自己阅读!

以前看正则表达式,但没有注意到正则表达式的贪婪与非贪婪模式,今天在经典上看到了这么段代码:
<script>
try{
str="<p>abcdefg</p><p>abcdefghijkl</p>";

re1=str.match(/<p>[\W\w]+?<\/p>/ig);
alert("非贪婪模式:\r\n\r\n1:"+re1[0]+"\r\n2:"+re1[1]);

re1=str.match(/<p>[\W\w]+<\/p>/ig);
alert("贪婪模式:\r\n\r\n"+re1);

re1=str.match(/<p>(.+?)<\/p>/i);
alert("非贪婪模式,且不要标记:\r\n\r\n1:"+re1[1]);

re1=str.match(/<p>(.+)<\/p>/i);
alert("贪婪模式,且不要标记:\r\n\r\n"+re1[1]);
}catch(e){alert(e.description)}
</script>

 匹配次数中的贪婪与非贪婪

    在使用修饰匹配次数的特殊符号时,有几种表示方法可以使同一个表达式能够匹配不同的次数,比如:"{m,n}", "{m,}", "?", "*", "+",具体匹配的次数随被匹配的字符串而定。这种重复匹配不定次数的表达式在匹配过程中,总是尽可能多的匹配。比如,针对文本 "dxxxdxxxd",举例如下:

表达式

匹配结果

(d)(\w+)

"\w+" 将匹配第一个 "d" 之后的所有字符 "xxxdxxxd"

(d)(\w+)(d)

"\w+" 将匹配第一个 "d" 和最后一个 "d" 之间的所有字符 "xxxdxxx"。虽然 "\w+" 也能够匹配上最后一个 "d",但是为了使整个表达式匹配成功,"\w+" 可以 "让出" 它本来能够匹配的最后一个 "d"

    由此可见,"\w+" 在匹配的时候,总是尽可能多的匹配符合它规则的字符。虽然第二个举例中,它没有匹配最后一个 "d",但那也是为了让整个表达式能够匹配成功。同理,带 "*" 和 "{m,n}" 的表达式都是尽可能地多匹配,带 "?" 的表达式在可匹配可不匹配的时候,也是尽可能的 "要匹配"。这 种匹配原则就叫作 "贪婪" 模式 。

    非贪婪模式:

    在修饰匹配次数的特殊符号后再加上一个 "?" 号,则可以使匹配次数不定的表达式尽可能少的匹配,使可匹配可不匹配的表达式,尽可能的 "不匹配"。这种匹配原则叫作 "非贪婪" 模式,也叫作 "勉强" 模式。如果少匹配就会导致整个表达式匹配失败的时候,与贪婪模式类似,非贪婪模式会最小限度的再匹配一些,以使整个表达式匹配成功。举例如下,针对文本 "dxxxdxxxd" 举例:

表达式

匹配结果

(d)(\w+?)

"\w+?" 将尽可能少的匹配第一个 "d" 之后的字符,结果是:"\w+?" 只匹配了一个 "x"

(d)(\w+?)(d)

为了让整个表达式匹配成功,"\w+?" 不得不匹配 "xxx" 才可以让后边的 "d" 匹配,从而使整个表达式匹配成功。因此,结果是:"\w+?" 匹配 "xxx"

    更多的情况,举例如下:

    举例1:表达式 "<td>(.*)</td>" 与字符串 "<td><p>aa</p></td> <td><p>bb</p></td>" 匹配时,匹配的结果是:成功;匹配到的内容是 "<td><p>aa</p></td> <td><p>bb</p></td>" 整个字符串, 表达式中的 "</td>" 将与字符串中最后一个 "</td>" 匹配。

    举例2:相比之下,表达式 "<td>(.*?)</td>" 匹配举例1中同样的字符串时,将只得到 "<td><p>aa</p></td>", 再次匹配下一个时,可以得到第二个 "<td><p>bb</p></td>"。

好东西共享出来 JGsoft RegexBuddy v3.1.0 零售版 (破解版)

RegexBuddy 使用 RegexBuddy 可以创建规范的正规表达式。该软件可以编译由其它软件编写的复杂的正规表达式。使用纯英文模块代替标准的 Regex 语法。在样本行和文件上测试任何规范的表达式,防止在实际数据上发生错误。通过分步完成真实的匹配过程来进行调试而不是单凭猜测。可以在你的自动地调整为 C#, VB.NET, Java, C, C++, Delphi, Perl, PHP, Python, JavaScript 和 Ruby 特殊细节的带有代码摘录的源代码当中使用 RegexBuddy

补充一下,前几天,我下载了RegexBuddy3.4的,用了一段时间,提示

Thank you for taking the time to thoroughly evaluate RegexBuddy.
I am sure RegexBuddy’s assistance has been very valuable to you already.
This free evaluation version has now expired. Click the Help button and then Buy RegexBuddy to open RegexBuddy’s web site. You will get up to date pricing information and a complete list of purchasing options.
If you buy online with a credit card, you can download your personal unlimited version of RegexBuddy immediately after entering your credit card details on our safe and secure order form.
You can buy RegexBuddy with or without a credit card, either directly from us, or from one of our many resellers.

过期了。。我汗,所以,建议大家不要更新了,这个版本就很好用的。。。。
2010-07-01 18:20 更新。

正则表达式工具regexbuddy免费破解版下载

正则表达式工具regexbuddy免费破解版下载

点击下载