应该如何构造复杂的正则表达式

文题本来是《如何构造复杂的正则表达式》,但是觉得有些歧义,就感觉正则式本来很简单,我在教人如何将它小事化大一样。正好相反,我的本意是说,即使复杂的正则式也不怕,找出合适的方法,将其构造出来。

避重就轻

Snopo给出的文本是这样的:or and name='zhangsan' and id=001 or age>20 or area='%renmin%' and like,问,如何提取其中正确的SQL查询语句。

简要分析可知,中间部分是合乎要求的,只是两端的有若干个like, or, and。构造能够解析合乎SQL语法的查询语句的正则表达式,应该是比较复杂的。可是,对于具体的问题,也可以更简单。上述的不良构的SQL语句,应该是使用程序自动生成的,它的两端会有一些不符合题意的文本。只要将这些文本去除就可以了。

于是,我写出了正则表达式:s/^(?:(?:or|and|like)\s*)+|\s*(?:(?:or|and|like)\s*)+$//mi;,这样就把多行字串首尾的like, or, and以及可能的空白字符全部去掉了,剩下的内容即为所求。

分而治之

答案发过去之后,Snopo显然不是很满意这种“偷懒”的办法。他继续问道,能否写出正则式,用来匹配合符SQL语法要求的条件查询语句?(只考虑where部分即可,不必写完整的select。)

的确,从快速解决问题的角度来说,只要能够行之有效地解决,用什么办法都可以;不过从学习知识的角度来说,不避重就轻,而是刨根问底,才是正途。既如此,就看一下如何使用正则,将该SQL查询语句解决掉。

最简单的查询语句,应该是真假判断,即 where 1; where True; where false,等等。 这样的语句使用正则式,直接/(?:-?\d+|True|False)/i。

稍复杂些的单条语句,可以是左右比较,即

代码如下:

name like 'zhang%', 或 age>25 ,或 work in ('it', 'hr', 'R&D')

。将其简单化,结构就变为A OP B。其中A代表变量,OP代表比较操作符,B代表值。

•A: 最简单的A,应该是\w+。考虑到实际情况,变量包含点号或脱字符,例如`table.salary`,可以记为/[\w.`]+/。这是比较笼统的细化。如果要求比较苛刻,还可以做到让脱字符同时在左右两边出现(条件判断)。
•OP: Where 常用的几种关系比较为:=, <>, >, <, >=, <=, Between, Like, in。使用简单的正则描述之,成为:/(?:[<>=]{1,2}|Between|Like|In)/i。
•B: B 的情况又可分为3种:变量,数字,字符串,列表。为简单起见,这里就不考虑算术表达式了。

◦变量的话,直接延用A的定义即可。不赘述。
◦数字:使用/\d+/来定义。不考虑小数和负数了。

◦字符串:包括单引号字串和双引号字串。中间可以包括被转义的引号。我写了一个符合这一要求的引号字串正则表达式,形如:/(['"])(?:\\['"]|[^\\1])*?\1/。不过,由于它只是庞大机器的一个零件,这样写的风险是极其大的。首先,它使用了反向引用;其次,该反向引用使用了全局的反向引用编号。我写了自动生成全局编号的函数,来解决这一问题。不过,这里谈细节是不是太深入了。应该先谈框架,再说细节才对。不应该一入手就陷进细节的汪洋大海。

◦列表:列表是形如(1, 3 , 4) 或 ("it", "hr", "r&d")之类的东东,它由简单变量以逗号相连,两边加上括号组成。列表的单项以I表示,它代表 数字|字符串。此时,列表就变为:/\(I(?:,I)*?\)/。它表示,左括号,一个I,一系列由逗号、I组成的其它列表项(0个或多个),右括号。简单起见没有考虑空白字符。
•至此,可以总结出单条语句的正则框架:S =~ /A OP B/i。S在此代表单条语句。
更为复杂的是多条语句,可以由单条语句组成,中间使用 and 或 or 连接。合理地构造单条语句,将其稳定地编制为多条语句,任务就完成了。

沿用上面的示例,以S代表单条语句,那么复合语句C就是 C =~ S(?:(?:or|and) S)*?/。至此,一个初具规模的条件语句解析器就诞生了。下面以python为例,一步一步实现出来。

Python实现
重申一句:虽然给出了实现,但是仍请注重思路,忽略代码。


代码如下:

#!/usr/bin/python
# -*- coding: utf-8 -*-
#
#author: rex
#blog: http://iregex.org
#filename test.py
#created: 2010-08-06 17:12

#generage quoted string;
#including ' and " string
#allow \' and \" inside
index=0
def gen_quote_str():

global index
index+=1
char=chr(96+index)
return r"""(?P<quote_%s>['"])(?:\\['"]|[^'"])*?(?P=quote_%s)"""% (char, char)

#simple variable
def a():
return r'[\w.`]+'

#operators
def op():
return r'(?:[<>=]{1,2}|Between|Like|In)'

#list item within (,)
#eg: 'a', 23, a.b, "asdfasdf\"aasdf"
def item():
return r"(?:%s|%s)" % (a(), gen_quote_str())

#a complite list, like
#eg: (23, 24, 44), ("regex", "is", "good")
def items():
return r"""\( \s*
%s
(?:,\s* %s)* \s*
\)""" % (item(), item())

#simple comparison
#eg: a=15 , b>23
def s():
return r"""%s \s* %s \s* (?:\w+| %s | %s )""" % (a(), op(), gen_quote_str(), items())

#complex comparison
# name like 'zhang%' and age>23 and work in ("hr", "it", 'r&d')
def c():
return r"""
(?ix) %s
(?:\s*
(?:and|or)\s*
%s \s*
)*
""" % (s(), s())

print "A:\t", a()
print "OP:\t", op()
print "ITEM:\t", item()
print "ITEMS:\t", items()
print "S:\t", s()
print "C:\t", c()

该代码在我的机器上(Ubuntu 10.04, Python 2.6.5)运行的结果是:


代码如下:

A: [\w.`]+
OP: (?:[<>=]{1,2}|Between|Like|In)
ITEM: (?:[\w.`]+|(?P<quote_a>['"])(?:\\['"]|[^'"])*?(?P=quote_a))
ITEMS: \( \s*
(?:[\w.`]+|(?P<quote_b>['"])(?:\\['"]|[^'"])*?(?P=quote_b))
(?:,\s* (?:[\w.`]+|(?P<quote_c>['"])(?:\\['"]|[^'"])*?(?P=quote_c)))* \s*
\)
S: [\w.`]+ \s* (?:[<>=]{1,2}|Between|Like|In) \s* (?:\w+| (?P<quote_d>['"])(?:\\['"]|[^'"])*?(?P=quote_d) | \( \s*
(?:[\w.`]+|(?P<quote_e>['"])(?:\\['"]|[^'"])*?(?P=quote_e))
(?:,\s* (?:[\w.`]+|(?P<quote_f>['"])(?:\\['"]|[^'"])*?(?P=quote_f)))* \s*
\) )
C:
(?ix) [\w.`]+ \s* (?:[<>=]{1,2}|Between|Like|In) \s* (?:\w+| (?P<quote_g>['"])(?:\\['"]|[^'"])*?(?P=quote_g) | \( \s*
(?:[\w.`]+|(?P<quote_h>['"])(?:\\['"]|[^'"])*?(?P=quote_h))
(?:,\s* (?:[\w.`]+|(?P<quote_i>['"])(?:\\['"]|[^'"])*?(?P=quote_i)))* \s*
\) )
(?:\s*
(?:and|or)\s*
[\w.`]+ \s* (?:[<>=]{1,2}|Between|Like|In) \s* (?:\w+| (?P<quote_j>['"])(?:\\['"]|[^'"])*?(?P=quote_j) | \( \s*
(?:[\w.`]+|(?P<quote_k>['"])(?:\\['"]|[^'"])*?(?P=quote_k))
(?:,\s* (?:[\w.`]+|(?P<quote_l>['"])(?:\\['"]|[^'"])*?(?P=quote_l)))* \s*
\) ) \s*
)*

请看匹配效果图:

算术表达式

我记得刚才好像提到“为简单起见,这里就不考虑算术表达式了”。不过,解析算术表达式是个非常有趣的话题,只要是算法书,都会提及(中缀表达式转前缀表达式,诸如此类)。当然它也可以使用正则表达式来描述。

其主要思路是:

代码如下:

expr -> expr + term | expr - term | term
term -> term * factor | term / factor | factor
factor -> digit | ( expr )

以及代码:


代码如下:

#!/usr/bin/python
# -*- coding: utf-8 -*-
#
#author: rex
#blog: http://jb51.net
#filename math.py
#created: 2010-08-07 00:44

integer=r"\d+"

factor=r"%s (?:\. %s)?" % (integer, integer)

term= "%s(?: \s* [*/] \s* %s)* " % (factor, factor)

expr= "(?x) %s(?: \s* [+-] \s* %s)* " % (term, term)

print expr

看一下它的输出和匹配效果图:

小贴士

•如果不用复杂的正则式就能解决问题,一定不要用。
•如果必须写比较复杂的正则式,请参考以下原则。
•从大处着眼,先理解待解析的文本的整体结构是什么样子,划分为小部件;
•从细处着手,试图实现每一个小部件,力求每一部分都是完整、坚固的,且放在全局也不会冲突。
•合理组装这些部件。
•分而治之的好处:只有某个模块出错,其它部分没错时,可以迅速定位错误,消除BUG。
•谨慎使用捕获括号,除非你知道自己在做什么,知道它会有什么副作用,以及是否有可行的解决措施。对于短小的正则式来说,一两个多余的括号是无伤大雅的;但是对于复杂的正则式来说,一对多余的括号可能就是致命的错误。
•尽量使用free-space模式。此时你可以自由地添加注释和空白字符,以便提高正则表达式的可读性。

时间: 2012-09-07

Java 正则表达式详解

如果你不熟悉这个术语,那么"正则表达式"(Regular Expression)就是一个字符构成的串,它定义了一个用来搜索匹配字符串的模式. 正则表达式30分钟入门教程 常用正则表达式 许多语言,包括Perl.PHP.Python.JavaScript和JScript,都支持用正则表达式处理文本,一些文本编辑器用正则表达式实现高级"搜索-替换"功能.那么Java又怎样呢?本文写作时,一个包含了用正则表达式进行文本处理的Java规范需求(Specification R

JAVA中正则表达式小总结(整理)

]js正则表达式基本语法(精粹):  http://www.jb51.net/article/72044.htm 许多语言,包括Perl.PHP.Python.JavaScript和JScript,都支持用正则表达式处理文本,一些文本编辑器用正则表达式实现高级"搜索-替换"功能.所以JAVA语言也不例外.正则表达式已经超出了某种语言或某个系统的局限,成为被人们广为使用的工具,我们完全可以用它来解决实际开发中碰到的一些实际的问题. 在JDK1.3及之前的JDK版本中并没有包含正则表达式的

ASP.NET 中的正则表达式

摘要:正则表达式是一种处理文本的有用工具.无论是验证用户输入.搜索字符串内的模式.还是以各种有效方式重新设置文本格式,正则表达式都非常有用.下载本文的源代码.引言Microsoft®.NET Framework 对正则表达式的支持是一流的,甚至在 Microsoft® ASP.NET 中也有依赖正则表达式语言的控件.本文介绍了深入学习正则表达式的基础知识和推荐内容. 本文主要面向对正则表达式知之甚少或没有使用经验,但却熟悉 ASP.NET.可借助 .NET 编程的初学者.此外,希望本文连同 re

正则表达式教程之元字符的使用详解

本文实例讲述了正则表达式教程之元字符的使用.分享给大家供大家参考,具体如下: 注:在所有例子中正则表达式匹配结果包含在源文本中的[和]之间,有的例子会使用Java来实现,如果是java本身正则表达式的用法,会在相应的地方说明.所有java例子都在JDK1.6.0_13下测试通过. 一.对特殊字符进行转义 元字符是一些在正则表达式里有着特殊含义的字符.因为元字符在正则表达式里有着特殊的含义,所以这些字符就无法用来代表它们本身.在元字符前面加上一个反斜杠就可以对它进行转义,这样得到的转义序列将匹配那

Java正则表达式入门学习

许多语言,包括Perl.PHP.Python.JavaScript和JScript,都支持用正则表达式处理文本,一些文本编辑器用正则表达式实现高级"搜索-替换"功能.所以JAVA语言也不例外.正则表达式已经超出了某种语言或某个系统的局限,成为被人们广为使用的工具,我们完全可以用它来解决实际开发中碰到的一些实际的问题. 一.正则表达式基础知识 1.1 句点符号 假设你在玩英文拼字游戏,想要找出三个字母的单词,而且这些单词必须以"t"字母开头,以"n"

JAVA 正则表达式陈广佳版本(超详细)

在Sun的Java JDK 1.40版本中,Java自带了支持正则表达式的包,本文就抛砖引玉地介绍了如何使用java.util.regex包. 可粗略估计一下,除了偶尔用Linux的外,其他Linu x用户都会遇到正则表达式.正则表达式是个极端强大工具,而且在字符串模式-匹配和字符串模式-替换方面富有弹性.在Unix世界里,正则表达式几乎没有什么限制,可肯定的是,它应用非常之广泛. 正则表达式的引擎已被许多普通的Unix工具所实现,包括grep,awk,vi和Emacs等.此外,许多使用比较广泛

Python实现简易Web爬虫详解

简介: 网络爬虫(又被称为网页蜘蛛),网络机器人,是一种按照一定的规则,自动地抓信息的程序或者脚本.假设互联网是一张很大的蜘蛛网,每个页面之间都通过超链接这根线相互连接,那么我们的爬虫小程序就能够通过这些线不断的搜寻到新的网页. Python作为一种代表简单主义思想的解释型.面向对象.功能强大的高级编程语言.它语法简洁并且具有动态数据类型和高层次的抽象数据结构,这使得它具有良好的跨平台特性,特别适用于爬虫等程序的实现,此外Python还提供了例如Spyder这样的爬虫框架,BeautifulSo

使用JS中的exec()方法构造正则表达式验证

正则表达式,又称正规表示法.常规表示法.(英语:Regular Expression,在代码中常简写为regex.regexp或RE),计算机科学的一个概念.正则表达式使用单个字符串来描述.匹配一系列符合某个句法规则.在很多文本编辑器里,正则表达式通常被用来检索.替换那些符合某个模式的文本. 一.Javascript中的正则表达式 在Javascript中,可以使用RegExp对象构造正则表达.我们需要新建一个实例化的RegExp()对象,可以传入两个参数:第一个参数是匹配的模式,第二个参数是一

正则表达式中test、exec、match的区别介绍及括号的用法

test.exec.match的简单区别 1.test test 返回 Boolean,查找对应的字符串中是否存在模式. var str = "1a1b1c"; var reg = new RegExp("1.", ""); alert(reg.test(str)); // true 2.exec exec 查找并返回当前的匹配结果,并以数组的形式返回. var str = "1a1b1c"; var reg = new Re