说到模式匹配很多人一定会想到POSIX规范的正则表达式,也许看到这篇文章的你是这里的“很多人”中的一个,但是我还是打算先介绍一下正则表达式,因为可能有些XDJM没有接触过正则表达式。
百度百科里是这么定义正则表达式的:在计算机科学中,是指一个用来描述或者匹配一系列符合某个句法规则的字符串的单个字符串。在很多文本编辑器或其他工具里,正则表达式通常被用来检索和/或替换那些符合某个模式的文本内容。许多程序设计语言都支持利用正则表达式进行字符串操作。
正则表达式是一个模式匹配的公式,使用简单,能够快速有效的处理字符串,它正被越来越多的文本编辑软件,类库,脚本工具所支持,有兴趣的朋友可以去查下GRETA或者Boost::regex,这两个是实现正则表达式的最有名的类库。由于POSIX规范的正则表达式非常复杂,要实现这样的功能要编写大量的代码,lua决定只实现部分规范。然而lua的模式匹配功能非常强大,并且包含了一些使用标准POSIX模式匹配不容易实现的功能。
下面开始介绍lua的模式匹配:
string.find (s, pattern [, init [, plain]])
string.gsub (s, pattern, repl [, n])
string.match (s, pattern [, init])
这几个string库函数都使用了相同的参数pattern,表示模式串的意思,分别提供了模式搜索,模式匹配的功能
(http://www.lua.org/manual/5.1/manual.html)。
字符类是用来表示一个字符集。以下所有的组合都是允许用来描述一个字符类的:
(1) x --(x不是^$()%.[]*+-?当中的字符)代表字符x本身;
(2) . --与任何字符配对
(3) %a --与任何字母配对
(4) %c --与任何控制字符配对(例如\n)
(5) %d --与任何数字配对
(6) %l --与任何小写字母配对
(7) %p --与任何标点符号配对
(8) %s --与空白字符配对
(9) %u --与任何大写字母配对
(10)%w --与任何字母数字配对
(11)%x --与任何十六进制数配对
(12)%z --与任何代表0的字符配对
(13)%x --(此处x是非字母非数字字符):与字符x配对,主要用来处理表达式中有功能的字符(^$()%.[]*+-?),如%%表示‘%’这个特殊字符
(14)[set] --与set中包含的任何字符匹配,可以指定一定范围的字符,范围的结尾用字符‘-’来分离,如[0-7]表示8进制的数字;可以使用以上所有的字符类,如[%w_]表示与所有的字母数字或者下划线‘_’配对,[0-7%l-]表示与所有的8进制数字,或者与所有的小写字母,或者与字符‘-’都可以配对
(15)[~set] --跟上面的(14)相反,与任何不包含在set集合内的字符类配对
当上面的(3)~(12)表示的字符类换成大写表示时,表示与非该字符类的任何字符匹配,如%S表示与所有的非空白字符配对。字母、空格和其他字符组合的定义依赖于当前环境,一般情况下,[a-z]与%l表示的字符集不一样。
模式项:
一个模式项可以有一下表示方式:
(1)一个单独的字符类
(2)一个单独的字符类后面加一个字符'*',匹配0次或多次前面的字符类,总是尽可能进行最长的匹配
(3)一个单独的字符类后面加一个字符'+',匹配1次或多次前面的字符类,总是尽可能进行最长的匹配
(4)一个单独的字符类后面加一个字符'-',匹配0次或多次前面的字符类,与'*'的区别是它总是尽可能进行最短的匹配
(5)一个单独的字符类后面加一个字符'?',匹配0次或者1次前面的字符类
(6)%n, n是1到9的字符,表示第n个capture项,看下面的Captrue。
(7)%bxy, 其中x和y是不同的字符,这样的模式项是以x为开始,y为结尾的字符串,比如'%b()'表示‘('开头‘)’结尾的字符串,一般常用的有'%b[]', ‘%b{}’, ‘%b<>’,
print(string.gsub("a (enclosed (in) parentheses) line", "%b()", "new"))
-->a new line
模式串:
模式串就是模式项的集合。以'^'开头的模式只匹配目标串的开始部分,相似的,以 '$' 结尾的模式只匹配目标串的结尾部分。如:
if string.find(s, "^%d") then ...
检查字符串是否以数字开头
if string.find(s, "^[+-]?%d+$") then ...
检查字符串s是否是一个整数
Capture:
一个模式串可以有子模式串,子模式串使用小括号分开,我们称为capture(捕获)。当匹配成功时,lua会把匹配的capture保存起来一遍下次使用。
好了,关于模式串的介绍就到这里了,下面介绍一下上面提到的string库的4个函数的用法:
1.string.find (s, pattern [, init [, plain]])
这个函数在字符串s中查找与pattern模式匹配的字串,如果匹配到相应的字串则返回该字串的开始索引和结束索引两个值,否则返回nil。init,可选的第三个参数,可以指定开始查找的位置,默认从索引1开始查找。第四个参数plain是一个布尔型,也是可选的,默认是true,打开模式匹配开关,如果设为false则关闭模式开关,pattern就被当作是简单的字符串,而不当作特殊的模式,这样就会直接搜索子串。
这个函数返回一个迭代器函数,每次调用都会返回下一个captures,如果pattern没有指定capture,则每次调用都返回匹配的整个字串,如:
s = "hello world from Lua"
for w in string.gmatch(s, "%a+") do
print(w)
end
这个每次循环都会提取下一个单词并打印出来
t = {}
s = "from=world, to=Lua"
for k, v in string.gmatch(s, "(%w+)=(%w+)") do
t[k] = v
end
x = string.gsub("hello world", "(%w+)", "%1 %1")
--> x="hello hello world world"
第一次匹配到的是hello,capture捕获到的第一项也是hello,而两个%1就是两个hello,所以替换的结果是每个字重复写一次。如果把%1改成%2,那么就会出错,因为pattern里只有一个小括号,所以每次captrue捕获的只有一项,没有第二项,就会有非法的capture索引这个错误。
x = string.gsub("hello world from Lua", "(%w+)%s*(%w+)", "%2 %1")
--> x="world hello Lua from"
经过上面的例子,这个例子很容易看出来是把前后两个字翻过来换成以后一前了。
x = string.gsub("$name-$version.tar.gz", "%$(%w+)", t)
print(x)
x = string.gsub("home = $HOME, user = $USER", "%$(%w+)", os.getenv)
print(x) --> "home = /home/roberto, user = roberto"
第一次匹配对象是$HOME,捕获(capture)到的对象是HOME ("%$(%w+)"pattern中小括号匹配到的结果),所以第一次匹配时调用os.getenv("HOME"),返回的结果替换掉匹配对象$HOME; 同样第二次匹配到$USER时调用os.getenv("USER"),返回的结果是"roberto",替换掉了以前的$USER