模式匹配函数
函数find、gsub、match、gmatch
函数find
函数string.find用于在指定的目标字符串中搜索指定的模式。
最简单的模式是,查找字符串,会返回两个值:匹配到模式开始位置的索引和结束位置的索引。如果没有找到任何匹配,则返回nil
s = "hello world"
i, j = string.find(s, "hello")
print(i, j) --> 1 5 Lua索引是从1开始
print(string.sub(s, i, j)) --> hello 查找第i到j的字符
print(string.find(s, "world")) --> 7 11
i, j = string.find(s, "l")
print(i, j) --> 3 3
print(string.find(s, "lll")) --> nil
函数string.find
具有两个可选参数。
第3个参数是一个索引,用于说明从目标字符串的哪个位置开始搜索。
第4个参数是一个布尔值,用于说明是否进行简单搜索。字如其名,所谓简单搜索就是忽略模式而在目标字符串中进行单纯的“查找子字符串”的动作
string.find("a [word]", "[") -->报错 malformed pattern (missing ']')
string.find("a [word]", "[", 1, true) -->3 3
由于'['在模式中具有特殊含义,因此第1个函数调用会报错。在第2个函数调用中,函数只是把'['当作简单字符串。请注意,如果没有第3个参数,是不能传入第4个可选参数的。
函数match
也是字符串搜索模式,不过返回的是目标字符串中与模式相匹配的那部分子串,而非该模式所在的位置
print(string.match("hello world", "hello"))--> 匹配hello hello
一般用于字符串匹配,类似其它语言的正则匹配,比如:匹配是否是手机号码,是否是邮箱地址等
date = "Today is 17/7/1990"
d = string.match(data, "%d+/%d+/%d+")
print(d) -->匹配数字 17/7/1990
函数gsub
函数string.gsub有3个必选参数:目标字符串、模式和替换字符串
其基本用法是:将目标字符串中所有出现模式的地方换成替换字符串
s = string.gsub("Lua is cute", "cute", "great")
print(s) --> Lua is great
s = string.gsub("all lii", "l", "x")
print(s) --> axx xii
s = string.gsub("Lua is great", "Sol", "Sun")
print(s) --> Lua is great
此外,该函数还有一个可选的第4个参数,用于限制替换的次数
s = string.gsub("all lii", "l", "x", 1)
print(s) --> axl lii
s = string. gsub("all lii", "l", "x", 2)
print(s) --> axx lii
除了替换字符串以外,string.gsub
的第3个参数也可以是一个函数或一个表,这个函数或表会被调用(或检索)以产生替换字符串;
函数string.gsub还会返回第2个结果,即发生替换的次数。
gmatch
函数string.gmatch返回一个函数,通过返回的函数可以遍历一个字符串中所有出现的指定模式。
例如,以下示例可以找出指定字符串s中出现的所有单词
s = "some string"
words = {}
for w in string.gmatch(s,"%a+") do
words[#words + 1]= w
end
匹配模式
使用%
作为转义符
符号 | 描述 |
---|---|
. | 任意字符 |
%a | 字母 |
%c | 控制字符 |
%d | 数字 |
%g | 除空格外的可打印字符 |
%l | 小写字母 |
%p | 标点符号 |
%s | 空白字符 |
%u | 大写字母 |
%w | 字母和数字 |
%x | 十六进制数字 |
%A | 任意非字母的字符 |
print(string.gsub("hello, up-down", "%A", ".")) --> hello..up.down
魔法字符
( ) . % + - * ? [ ] ^ $
如:
%?
匹配问号,%%
匹配百分号
可以使用字符集来创建自定义的字符分类,只需要在方括号内将单个字符和字符分类组合起来即可
如:字符集
[%w_]
匹配所有下划线结尾的字母和数字、[01]
匹配二进制数组、[%[%]]
匹配方括号
例如:想统计一段文本中元音得到数量
_, nvow = string.gsub(text, "[AEIOUaeiou]", "")
-字符
用-
符号将字符范围的第一个字符和最后一个字符并用横线将它们连接在一起
如:
%d
相当于[0-9]
、%x
相等于[0-9a-fA-F]
在字符集前加一个补字符^
就可以得到这个字符集对应的补集,简单的分类可以使用大写形式来获得对应的补集(如:%S
要比[^%s]
好)
如:模式
[0-7]
代表所有八进制数字以外的字符、模式[A\n]
则代表除换行符以外的其他字符。
通过描述模式中重复和可选部分的修饰符来让模式更加有用。
字符 | 描述 |
---|---|
+ | 重复一次或多次 |
* | 重复零次或多次 |
- | 重复零次或多次(最小匹配) |
? | 可选(出现零次或一次) |
+字符
修饰符+
匹配原始字符分类中的一个或多个字符,它总是获取与模式相匹配的最长序列。
例如,模式'%a+'代表一个或多个字母(即一个单词):
print((string.gsub("one, and two; and three", "%a+", "word"))) --> word , word word; word word
模式
%d+
匹配一个或多个数字print(string.match("the number 1290 is even", "%d+")) --> 1290
*字符
修饰符*
类似于修饰符+
,但是它还接受对应字符分类出现零次的情况。该修饰符一个典型的用法就是在模式的部分之间匹配可选的空格。
例如,为了匹配像()或( )这样的空括号对,就可以使用模式
%(%s*%)
,其中的%s*
匹配零个或多个空格(括号在模式中有特殊含义,所以必须进行转义)。
另一个示例是用模[_%a][_%w]*
匹 Lua程序中的标识符:标识符是一个由字母或下画线开头,并紧跟零个或多个由下画线、字母或数字组成的序列。
修饰符-
和修饰符*
类似,也是用于匹配原始字符分类的零次或多次出现。不过,跟修饰符*
总是匹配能匹配的最长序列不同,修饰符-
只会匹配最短序列。
例如:当试图用模式
[_%a][_%w]-
查找标识符时,由于[_%w]-
总是匹配空序列,所以我们只会找到第一个字母。
又如,假设我们想要删掉某C语言程序中的所有注释,通常会首先尝试使用/%*.*%*/
(即/*
和*/
之间的任意序列,使用恰当的转义符对*
进行转义)。然而,由于.*
会尽可能长地匹配,因此程序中的第一个/*
只会与最后一个*/
相匹配
test = "int x; /* x */ int y; /* y */"
print(string.gsub(test, "/%*.*%*/", "")) -->int x
相反,模式.-
则只会匹配到找到的第一个*/
,这样就能得到期望的结果
test = "int x; /* x */ int y; /* y */"
print((string.gsub(test, "/%*.-%*/", ""))) -->int x; int y
?字符
修饰符?
可用于匹配一个可选的字符。
例如,假设我们想在一段文本中寻找一个整数,而这个整数可能包括一个可选的符号,那么就可以使用模式
[+-]?%d+
来完成这个需求,该模式可以匹配像"-12"、"23”和"+1009”这样的数字。其中,字符分类[+-]
匹配加号或减号,而其后的问号则代表这个符号是可选的。
Lua语言中的修饰符只能作用于一个字符模式,而无法作用于一组分类。
例如,我们不能写出匹配一个可选的单词的模式(除非这个单词只由一个字母组成)
^字符
以补字符^
开头的模式表示从目标字符串的开头开始匹配。类似地,以$
结尾的模式表示匹配到目标字符串的结尾。
例如,如下的代码可以用来检查字符串s是否以数字开头:
if string.find(s, "^%d") then...
如下的代码用来检查字符串是否为一个没有多余前缀字符和后缀字符的整数:
if string.find(s, "^[+-]?%d+$") then...
^
和$
字符只有位于模式的开头和结尾时才具有特殊含义;否则,它们仅仅就是与其自身相匹配的普通字符。
%b字符
模式%b
匹配成对的字符串,它的写法是%bxy
,其中×和y是任意两个不同的字符,x作为起始字符而y作为结束字符。
例如,模式
%b()
匹配以左括号开始并以对应右括号结束的子串:s = "a (enclosed (in) parentheses) line" print((string.gsub(s, "%b()", ""))) --> a line
%f字符
模式'%f[char-set]'代表前置模式。该模式只有在后一个字符位于char-set
内而前一个字符不在时匹配一个空字符串:
s = "the anthem is the theme"
print(string.gsub(s, "%f[%w]the%f[%W]", "one")) -->one anthem is one theme
模式
%f[%w]
匹配位于一个非字母或数字的字符和一个字母或数字的字符之间的前置,而模式%f[%W]
则匹配一个字母或数字的字符和一个非字母或数字的字符之间的前置。因此,指定的模式只会匹配完整的字符串"the"
请注意,即使字符集只有一个分类,也必须把它用括号括起来。
前置模式把目标字符串中第一个字符前和最后一个字符后的位置当成空字符(ASCII编码的\0)。在前例中,第一个"the"在不属于集合[%w]
的空字符和属于集合[%w]
的t之间匹配了一个前置。
捕获
捕获机制允许根据一个模式从目标字符串中抽出与该模式匹配的内容来用于后续用途,可以通过把模式中需要捕获的部分放到一对圆括号内来指定捕获。
使用函数:string.match
对于具有捕获的模式,函数string.match
会将所有捕获到的值作为单独的结果返回;换句话说、该函数会将字符串切分成多个被捕获的部分:
pair = "name = Anna"
key, vaule = string.match(pair, "(%a+)%s*=%s*(%a+)")
print(key, value) --> name Anna
模式
%a+
表示一个非空的字母序列,模式%s*
表示一个可能为空的空白序列。因此,上例中的这个模式表示一个字母序列、紧跟着空白序列、一个等号、空白序列以及另一个字母序列。模式中的两个字母序列被分别放在圆括号中,因此在匹配时就能捕获到它们。
下面是一个类似的示例:
date = "Today is 17/7/1990"
d, m, y = string.match(date, "(%d+)/(%d+)/(%d+)")
print(d, m, y) --> 17 7 1990
在模式中,形如
%n
的分类(其中n是一个数字),表示匹配第n个捕获的副本。
s = [[then he said: "it 's all right"!]]
q,quotedPart = string.match(s,"([\"'])(.-)%1")
print(quotedPart) --> it 's all right
print(q) --> "
第1个捕获是引号本身,第2个捕获是引号中的内容(与
.-
匹配的子串)。
下例是一个类似的示例,用于匹配Lua语言中的长字符串的模式:
"%[(=*)%[(.-)%]%1%]"
它所匹配的内容依次是:一个左方括号、零个或多个等号、另一个左方括号、任意内容(即字符串的内容)、一个右方括号、相同数量的等号及另一个右方括号
p = "%[(=*)%[(.-)%]%1%]" s = "a = [=[[[ something ]] ]==] ]=]; print(a)" print(string.match(s, p)) -->= [[ something ]] ]==] 第1个捕获是等号序列,第2个捕获是字符串内容。
函数gsub
像模式一样,替代字符串同样可以包括像%n
一样的字符分类,当发生替换时会被替换为相应的捕获。特别地,%0
意味着整个匹配,并且替换字符串中的百分号必须被转义为%%
。
下面这个示例会重复字符串中的每个字母、并且在每个被重复的字母之间插入一个减号:
print(string.gsub("hello Lua!", "%a", "%0-%0")) -->h-he-el-ll-lo-o L-Lu-ua-a
下例交换了相邻的字符:
print((string.gsub( "hello Lua", "(.)(.)","%2%1"))) --> ehll ouLa
替换
函数string.gsub
会在每次找到匹配时调用该函数,参数是捕获到的内容而返回值则被作为替换字符串。当第3个参数是一个表时,函数string.gsub
会把第一个捕获到的内容作为键,然后将表中对应该键的值作为替换字符串。如果函数的返回值为nil或表中不包含这个键或表中键的对应值为nil,那么函数gsub
不改变这个匹配。
下述函数用于变量展开,它会把字符串中所有出现的$varname
替换为全局变量varname
的值:
function expand(s)
return (string.gsub(s, "$(%w+)", _G))
end
name = "Lua"; status = "great"
print(expand("$name is $status, isn't it?")) -->Lua is great, isn't it?
_G
是预先定义的包括所有全局变量的表,对于每个与$(%w+)
匹配的地方($符号后紧跟一个名字),函数gsub
都会在全局表_G
中查找捕获到的名字,并用找到的结果替换字符串中相匹配的部分;如果表中没有对应的键,则不进行替换:print(expand("$othername is $status, isn't it?")) -->$othername is great, isn't it?