文章新地址
与其他几种脚本语言不通,Lua语言既没有使用POSIX正则表达式,也没有使用Perl正则表达式来进行模式匹配。之所以这样做的主要原因在于大小问题:一个典型的POSIX正则表达式实现需要超过4000行代码,这比所有Lua语言标准库总大小的一半还大。相比之下,Lua语言模式匹配的实现代码只有不到600行。尽管Lua语言的欧式匹配做不到完整POSIX实现的所有功能,但是Lua语言的模式匹配仍然非常强大,同时还具有一些与标准POSIX不同但又可与之媲美的功能。
模式匹配的相关函数
字符串标准库提供了基于模式的4个函数。我们已经初步了解过函数find和gsub,其余两个函数分别是match和gmatch。
函数string.find
函数string.find用于在指定的目标字符串中搜索指定的模式。最简单的模式就是一个单词,它智慧匹配到这个单词本身。例如,模式’hello’会在目标自服装中所搜子串"hello"。函数string.find找到一个模式后,会返回两个值:匹配到模式开始位置的索引和结束位置的索引。如果没有找到任何匹配,则返回nil:
s = "hello world"
i,j = string.find(s,"hello")
print(i,j) -- 1 5
print(string.sub(s,i,j)) -- hello
print(string.find(s,"world")) -- 7 11
i,j = string.find(s,"l")
print(i,j) -- 3 3
print(string.find(s,"lll")) -- nil
匹配成功后,可以以函数find返回的结果为参数调用函数string.sub来获取目标字符串匹配相应模式的子串。对于简单的模式来说,这一般就是模式本身。
函数string.find具有两个可选参数。第3个参数是一个索引,用于说明从目标字符串的哪个位置开始搜索。第4个参数是一个布尔值,用于说明是否进行简单搜索。所谓简单所有就是忽略模式而在目标字符串中进行单纯的“查找子字符串”的动作:
> string.find("a[word]","[")
stdin:1:malformed pattern (missing ']')
> string.find("a [word]","[",1,true) -- 3 3
由于’[‘在模式中具有特殊含义,因此第1个函数调用会报错。在第2个函数调用中,函数只是把’['当作简单字符串。请注意,如果没有第3个参数,是不能传入第4个可选参数的。
函数string.match
由于函数string.match也用于在一个字符串搜索模式,因此它与函数string.find非常相似。不过,函数string.match返回的是目标字符串中与模式相匹配的那部分子串,而非该模式所在的位置:
print(string.match("hello world","hello")) -- hello
对于诸如’hello’这样固定的模式,使用这个函数并没有什么意义。然而,当模式是变量时,这个函数的强大之处就显现出来了。例如:
date = "Today is 15/4/2020"
d = string.match(date,"%d + /%d + /%d +")
print(d) -- 15/4/2020
函数string.gmatch
函数string.gmatch返回一个函数,通过返回的函数可以遍历一个字符串中所有出现的指定模式。例如,以下示例可以找出字符串s中出现的所有单词:
s = "some string"
words = {
}
for w in string.gmatch(s,"%a+") do
words[#words + 1] = w
end
模式
大多数模式匹配库都是用反斜杠作为转义符。然后,这种方式可能会导致一些不良的后果。对于Lua语言的解析器而言,模式仅仅是普通的字符串。模式与其他的字符串一样遵循相同的规则,并不会被特殊对待;只有模式匹配相关的函数才会把它们当做模式进行解析。由于反斜杠是Lua语言中的转义符,所以我们应该避免它传递给任何函数。模式本身就难以阅读,到处把“\”换成“\”就更加火上浇油了。
我们可以使用双括号把模式括起来构成的长字符串来解决这个问题。然而,长字符串的写法对于通常比较短的模式而言又往往显得冗长。此外,我们还会失去在模式内进行转义的能力。
Lua语言的解决方案更加简单:Lua语言中的模式使用百分号作为转义符。总体上,所有被转义的字母都具有某些特殊含义,而所有被转移的非字母则代表其本身。
我们首先来学习字符分类的模式。所谓字符分类,就是模式中能够与一个特定集合中的任意字符相匹配的一项。例如,分类%d匹配的是任意数字。因此,可以使用模式’%d%d/%d%/d%d%d%d’来匹配dd/mm/yyyy格式的日期:
s = "Deadline is 15/04/2020, firm
date = "%d%d/%d%d/%d%d%d%d"
print(string.match(s,date)) -- 15/04/2020
下表列出了所有预置的字符串分类及其对应的含义:
. 任意字符
%a 字母
%c 控制字符
%d 数字
%g 除空格外的可打印字符
%l 小写字母
%p 标点符号
%s 空白字符
%u 大写字母
%w 字母和数字
%x 十六进制数字
这些类的大写形式表示类的补集。例如,%A
代表任意非字母的字符:
print((string.gsub("hello, up-down!","%A","."))) -- hello..up.down.
在输出函数gsub的返回结果时,我们使用了额外的括号来丢弃第二个结果,也就是特换发生的次数。
当在模式中使用时,还有一些被称为魔法字符的字符具有特殊含义。Lua语言的模式所使用的魔法字符包括:
()
.
%
+
-
*
?
[
]
^
$
正如我们之前已经看到的,百分号同样可以用于这些魔法字符的转义。因此,%?
匹配一个问号,%%
匹配一个百分号。我们不仅可以用百分号对魔法字符进行转义,还可以将其用于其他所有字母和数字外的字符。当不确定是否需要转义时,为了保险起见就可以使用转义符。
可以使用字符集来创建自定义的字符分类,只需要在方括号内将单个字符和字符分类组合起来即可。例如,字符集[%w_]
匹配所有以下画线结尾的字母和数字,[01]
匹配二进制数字,[%[%]]
匹配方括号。如果想要统计一段文本中元音的数量,可以使用如下的代码:
_,nvow