Lua 的字符串匹配与正则表达式
使用 lua 一段时间了,简单总结下 string 库中的几个与正则相关的函数。这些函数是 find、match、gmatch 和 gsub。然后是 lua 中支持的正则。文中的例子在 lua 5.3 的命令行中试验过。5.1 版的需要在提示符前加一个=号或加 return 空格。
string.find(s, pattern[, init[, plain]])
在字符串 s 中匹配 pattern,如果匹配成功返回第一个匹配到的子串的起始索引和结束索引,如果 pattern 中有分组,分组匹配的内容也会接着两个索引值之后返回。如果匹配失败返回 nil。
可选数值参数 init 表示从 s 中的哪个索引位置开始匹配,缺省值是 1,可以为负索引。
可选布尔值参数 plain 为 true 时,pattern 作为普通字符串匹配,所有正则中的元字符都只被作为普通字符解析。(这个参数并不是匹配字符串的结束索引)
string.find('Hanazawa Kana', 'na')
3 4
string.find('Hanazawa Kana', '[%a]+')
1 8
string.find('2015-5-12 13:53', '(%d+)-(%d+)-(%d+)')
1 9 2015 5 12
string.find('2015-5-12 13:53', '(%d+)-(%d+)-(%d+)', 1, true)
nil
string.find('%a1234567890%a', '%a', 3, true)
13 14
string.match(s, pattern[, init])
在字符串 s 中匹配 pattern,如果匹配失败返回 nil。否则,当 pattern 中没有分组时,返回第一个匹配到的子串;当 pattern 中有分组时,返回第一个匹配到子串的分组,多个分组就返回多个。可选参数 init 表示匹配字符串的起始索引,缺省为 1,可以为负索引。
> string.match('2015-5-12 13:53', '%d+-%d+-%d+')
2015-5-12
> string.match('2015-5-12 13:53', '(%d+)-(%d+)-(%d+)')
2015 5 12
> string.match('2015-5-12 13:53', '((%d+)-(%d+)-(%d+))')
2015-5-12 2015 5 12
可以发现,所有用到 match 的地方都可以用 find 来实现。match 是 find 的一个简化版
string.gmatch(s, pattern)
返回一个迭代器。每当迭代器调用时,返回下一个匹配到的子串,如果 pattern 中有分组,返回的是子串对应的分组。gmatch 也可以用 find 和循环来实现。
> for s in string.gmatch('2015-5-12 22:20', '%d+') do print(s) end
2015
5
12
22
20
> for s in string.gmatch('Hanazawa Kana', 'a(%a)a') do print(s) end --找出形如“a字母a”中间的字母
n
w
n
> for k, v in string.gmatch('a=214,b=233', '(%w+)=(%w+)') do print(k, v) end
a 214
b 233
看上面第二个测试例子,处于两个 a 字母中间的单个字母还有 z,但循环并没有输出。原因是在 ana 匹配成功之后,接下来匹配是从 z 开始的,z 没有被匹配到。正确的模式 pattern 应该不要捕获 a(%a)a 的后面的 a,用 python 的正则可以写成 a(\w)(?=a),他不会消耗掉后面的 a。但是 lua 不支持 (?=...)。(python 中 \w 表示单词字符 [a-zA-Z0-9_],记不清就把这类元字符列出来 如 %a 写成 [a-zA-Z])。
string.gsub(s, pattern, repl[, n])
替换字符串函数,这个功能应该是字符串处理中实用性最强的一个。
把字符串中用模式 pattern 匹配到的所有子串替换为 repl 指代的子串,返回替换后的字符串和替换的次数。可选数值参数n表示最多可替换的次数。
参数 repl 可以是正则表达式,也可以是函数。当 repl 是函数时,函数的参数是模式 pattern 捕获的子串,和 match 类似,有分组返回分组,无分组返回整个子串。函数最后应该返回一个字符串。如果 repl 是正则表达式,可以用分组序号引用匹配到的分组。
> string.gsub('Hanazawa Kana', 'na', 'nya')
Hanyazawa Kanya
> string.gsub('Hanazawa Kana', 'na', function(s) return string.sub(s,1,1)..'y'..string.sub(s,2,2) end)
Hanyazawa Kanya
> string.gsub('Hanazawa Kana', '(n)(a)', function(a,b) return a..'y'..b end)
Hanyazawa Kanya
> string.gsub('Hanazawa Kana', '(n)(a)', '%1y%2')
Hanyazawa Kanya
Lua 中的正则表达式
正则表达式由元字符按照规则(语法)组成。lua 中的特殊字符是 %.^$+-*?,一共12个。它们和一般字符按规则构成了 lua 的正则表达式。
元字符 | 描述 | 表达式实例 | 完整匹配的字串 |
---|---|---|---|
字符 | |||
普通字符 | 除去 %.[]()^$*+-? 的字符,匹配字符本身 | Kana | Kana |
. | 匹配任意字符 | Ka.a | Kana |
% | 转义字符,改变后一个字符的原有意思。当后面的接的是特殊字符时,将还原特殊字符的原意。% 和一些特定的字母组合构成了lua的预定义字符集。%和数字1~9组合表示之前捕获的分组 | K%wna %%na%% (a)n%1 | Kana %na% ana |
[...] | 字符集(字符类)。匹配一个包含于集合内的字符。[...] 中的特殊字符将还原其原意,但有下面几种特殊情况 1. %],%-,%^作为整体表示字符']','-','^' 2. 预定义字符集作为一个整体表示对应字符集 3. 当]位于序列的第一个字符时只表示字符']' 4. 形如 [^...],[...-...] 有特定的其他含义 | [a%]na [%a]na [%%a]na []]na [%]]na [a-]na | %na wna wna ]na ]na -na |
[...-...] | -表示ascii码在它前一个字符到它后一个字符之间的所有字符 | [a-z]a | na |
[^...] | 不在...中的字符集合。 | [^0-9]na [^^0-9]na | Kna Kna |
重复(数量词) | |||
* | 表示前一个字符出现0次或多次 | [0-9]* [a-z]*9* | 2009 na |
+ | 表示前一个字符出现1次或1次以上 | n+[0-9]+ | n2009 |
? | 表示前一个字符出现0次或1次 | n?[0-9]+ | 2009 |
预定义字符集 | |||
%s | 空白符 [\r\n\t\v\f] | an[%s]?9 | an 9 |
%p | 标点符号 | an[%p]9 | an.9 |
%c | 控制字符 | ||
%w | 字母数字 [a-zA-Z0-9] | [%w]+ | Kana9 |
%a | 字母 [a-zA-Z] | [%a]* | Kana |
%l | 小写字母 [a-z] | - | |
%u | 大写字母 [A-Z] | - | |
%d | 数字 [0-9] | - | |
%x | 16 进制数 [0-9a-fA-F] | - | |
%z | ascii 码是 0 的字符 | - | |
分组 | |||
(...) | 表达式中用小括号包围的子字符串为一个分组,分组从左到右(以左括号的位置),组序号从1开始递增。 | ab(%d+) (%d+)%1 | ab233 123123 |
边界匹配(属于零宽断言) | |||
^ | 匹配字符串开头 | ^(%a)%w* | abc123 |
$ | 匹配字符串结尾 | %w*(%d)$ | abc123 |
%b | |||
%bxy | 平衡匹配(匹配 xy 对)。这里的 x,y 可以是任何字符,即使是特殊字符也是原来的含义,匹配到的子串以 x 开始,以y结束,并且如果从 x 开始,每遇到x,计算+1,遇到 y 计数-1,则结束的y是第一个y使得计数等于0。就是匹配成对的符号,常见的如 %b() 匹配成对的括号 | %b() %d+%b() | (3+4(x*2)) 2(3+4(x*2)) |
备注
- lua 不支持分组后面接重复词(+*?),对于复杂的匹配可以用 find + 循环手动处理。
- %bxy跟预定义字符集有区别,前者在 [...] 仍保持原意,后者则失去特殊意义
- 上表中是 lua 对正则的支持,其他的正则如命名组,重复 {m, n} 等并不能在lua中用。注意转义字符是 % 不是 \
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论