最近学习了一下lua的正则表达式,在此记录一下。
为应对复杂多变的字符串匹配需求,很多语言都有对正则表达式的支持。Lua因为要保持简洁与小巧的设计目标,并没有像perl和python一样支持全部posix标准正则表达式规则。比如没有{n}
匹配n次的规则,字符{
和}
都只是作为普通字符存在,字符|
在posix标准正则表达式中表示或关系,在lua的pattern中也只是作为普通字符。
在lua中,一个正则表达式被称作为pattern,pattern中一对圆括号()包含的区域被称作为capture.
lua中一共有4个系统函数支持正则表达式参数,分别为string.find,string.match,string.gmatch,string.gsub
。
lua正则表达式应用的系统函数
string.find
函数的带参形式为string.find (s, pattern [, init [, plain]])
。函数有2个必要参数s和pattern,对应源字符串和相应的正则表达式,还有2个可选参数,init表示起始匹配位置,plain表示是否将pattern中的魔法字符无效化。
函数基本目标为,找到第一个匹配的位置。
来看官方文档对此函数的说明:
Looks for the first match of pattern in the string s. If it finds a match, then find returns the indices of s where this occurrence starts and ends; otherwise, it returns nil. A third, optional numeric argument init specifies where to start the search; its default value is 1 and can be negative. A value of true as a fourth, optional argument plain turns off the pattern matching facilities, so the function does a plain "find substring" operation, with no characters in pattern being considered magic. Note that if plain is given, then init must be given as well.If the pattern has captures, then in a successful match the captured values are also returned, after the two indices.
函数返回匹配的第一个字符串起始位置和结尾位置,如果有capture存在,则在之后返回capture匹配的值。
下面的代码演示了没有capture存在和有capture存在时的函数返回结果:
print(string.find('hello world','hello')) --> 1 5
print(string.find('hello world','(%a+)')) --> 1 5 hello
对于可选参数init和plain,以下代码作了示例:
print(string.find('hello (%a+) world','(%a+)',1,true)) --> 7 11
init = 1
表示从第一个字符'h'
开始搜索,plain = true
表示pattern中的魔法字符全部无效化,以普通字符处理,因此匹配的字符从第7个字符'('
开始,第11个字符')'
结束。
string.match
函数的带参形式为string.match (s, pattern [, init])
。函数基本目标为,寻找第一个匹配的字符串。
来看官方英文文档对此函数的说明:
Looks for the first match of pattern in the string s. If it finds one, then match returns the captures from the pattern; otherwise it returns nil. If pattern specifies no captures, then the whole match is returned. A third, optional numeric argument init specifies where to start the search; its default value is 1 and can be negative.
此函数返回pattern的第一个匹配,若不存在,则返回nil。
下面的代码示例了0个匹配,1个匹配,2个匹配时对应的函数返回结果:
print(string.match('hello world','renshaw')) --> nil
print(string.match('hello world','llo')) --> llo
print(string.match('hello world','(%a+)')) --> hello
- 第一行,没有匹配到字符串,返回nil。
- 第二行,pattern中没有作为capture的一对圆括号()出现,因此,它如果匹配到字符串,会返回全匹配。
- 第三行,
%a
表示匹配单个英文字母,%a+
表示匹配大于一个的英文字母,直到不是英文字母的字符(在这里是空格)出现或者到字符串结尾,(%a+)
表示将其中的%a+
作为一个capture,在此次匹配中一共匹配到两个capture,分别为'hello'
和'world'
,string.match
返回第一个capture,即'hello'
。
此函数还接受一个可选参数init,表示字符串匹配起始位置,个人觉得这个比较鸡肋,一般字符串在进行匹配之前,我们会进行预处理,比如使用string.sub进行截断。
以下代码演示了init值为3和8时的情况,因为第8个字符为'o'从这个字符开始匹配已经不满足'wor'的匹配要求:
print(string.match('hello world','wor',3)) --> wor
print(string.match('hello world','wor',8)) --> nil
string.gmatch
函数的带参形式为string.gmatch (s, pattern)
。函数基本目标为,对字符串进行全局匹配,返回匹配结果的枚举器。
来看官方英文文档对此函数的说明:
Returns an iterator function that, each time it is called, returns the next captures from pattern over the string s. If pattern specifies no captures, then the whole match is produced in each call.
此函数一般和for
语法配合使用进行匹配结果遍历,适用于将匹配结果进行提取的场景。
以下代码匹配字符串中每一个单词,然后输出:
for w in string.gmatch('hello world from lua', "%a+") do
print(w)
end
--> hello
--> world
--> from
--> lua
string.gsub
函数的带参形式为string.gsub (s, pattern, repl [, n])
。函数基本目标为,对字符串进行全局匹配并替换,返回替换后的字符串。
来看官方英文文档对此函数的说明:
Returns a copy of s in which all (or the first n, if given) occurrences of the pattern have been replaced by a replacement string specified by repl, which can be a string, a table, or a function. gsub also returns, as its second value, the total number of matches that occurred. The name gsub comes from Global SUBstitution.
If repl is a string, then its value is used for replacement. The character % works as an escape character: any sequence in repl of the form %d, with d between 1 and 9, stands for the value of the d-th captured substring. The sequence %0 stands for the whole match. The sequence %% stands for a single %.
If repl is a table, then the table is queried for every match, using the first capture as the key.
If repl is a function, then this function is called every time a match occurs, with all captured substrings passed as arguments, in order.
In any case, if the pattern specifies no captures, then it behaves as if the whole pattern was inside a capture.
If the value returned by the table query or by the function call is a string or a number, then it is used as the replacement string; otherwise, if it is false or nil, then there is no replacement (that is, the original match is kept in the string).
此函数参数s
为源字符串,pattern
为正则表达式,第三个参数repl
比较复杂,它可以是3种类型,string,table,function
,匹配完成之后,函数返回替换后的字符串,还有替换的次数。最后一个可选参数n规定了替换的次数上限。
如果repl
为function
类型,那么它接受n个参数(参数个数等于pattern中capture个数),分别对应pattern中capture的匹配值。
下面的代码分别演示了1个capture和2个capture对应的repl
匿名函数处理情况:
print(string.gsub('hello world from lua','(%a+)',function(x) return x .. 'c' end)) --> helloc worldc fromc luac 4
print(string.gsub('hello world from lua','(%a+)%s*(%a+)',function(x1,x2) return x2 .. x1 end)) --> worldhello luafrom 2
如果repl
为string
类型,则直接将匹配的值替换为repl
的值。
下面代码将所有匹配都替换为'c':
print(string.gsub('hello world from lua','(%a+)','c')) --> c c c c 4
以上代码的function
等效形式为:
print(string.gsub('hello world from lua','(%a+)',function(x) return 'c' end)) --> c c c c 4
如果repl
为table
类型,则将匹配的值k
替换为repl[k]
.
以下代码演示了repl
参数为table
的情况:
repl = {hello = 'he', world = 'wo', from = 'fr'}
print(string.gsub('hello world from lua','(%a+)',repl)) --> he wo fr lua 4
因为repl['lua']
为nil
,所以字符串lua
并没有被替换。
以上代码的function
等效形式为:
repl = {hello = 'he', world = 'wo', from = 'fr'}
print(string.gsub('hello world from lua','(%a+)',function(x) return repl[x] end)) --> he wo fr lua 4
我们需要注意string.gsub
替换的是匹配的全部值。来看一个示例:
repl = { from = 'to' }
print(string.gsub('hello world {from} lua','{(%a+)}',repl)) --> hello world to lua
以上代码不是将字符串中的'from'
替换为'to'
,而是将'{from}'
替换为'to'
,替换的是匹配的全部值。capture对应的值'from'
作为repl[k]
中的k
.
pattern解析
在lua的正则表达式中,规定了以下魔法字符().%+-*?[]^$
,数一数,一共12个,键盘上的标点符号!@#$%^&*()`~-_=+{}:"<>?[];',./|
一共32个,魔法字符的比例为37.5%,好吧,还好有些魔法字符必须以成对形式出现,我们一个一个来介绍。
%
这个字符,在pattern中为转义字符,比如%a
代表所有英文字母,但是不要忘记,pattern本身也是一个lua的字符串,字符串原本的转义字符也是支持的,因此pattern中就有了2个转义字符。
下面来看lua支持的%
转义有哪些:
字符 | 含义 |
---|---|
%a | 字母a-z,A-Z |
%b | %bxy,以x和y进行成对匹配 |
%c | 控制字符ASCII码 十进制转义表示为 - 31 |
%d | 数字 0 - 9 |
%f | %f[char-set],边界匹配,前一个不在范围内,后一个在 |
%g | 除了空格以外的可打印字符 十进制转义表示为33 - 126 |
%l | 小写字母 a - z |
%u | 大写字母 A - Z |
%s | 空格 十进制转义表示为32 |
%p | 标点符号,即!@#$%^&*()`~-_=+{}:"<>?[];',./| 32个字符 |
%w | 字母数字 a - z, A - Z, 0 - 9 |
%x | 十六进制符号 0 - 9, a - f, A - F |
除了以上表示的字符集,%
与魔法字符配合使用即可达到转义为原生字符的效果,比如%%
表示字符%
,%[
表示字符[
, %.
表示字符.
等等。
.
这个字符,在pattern中表示匹配所有字符,十进制表示为