Lua正则表达式匹配

最近学习了一下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规定了替换的次数上限。

如果replfunction类型,那么它接受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

如果replstring类型,则直接将匹配的值替换为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

如果repltable类型,则将匹配的值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码 十进制转义表示为\0 - \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中表示匹配所有字符,十进制表示为\0 - \255
  • ()圆括号组合字符,表示一个capture.
  • []方括号组合,表示数据集。[0-7]表示八进制数据集,[01]表示二进制数据集,[AEIOUaeiou]表示元音字母数据集。
  • [^]方括号加^表示数据集的补集,全集的范围是\0 - \255。例如,[^0-7]表示除了0-7外所有的字符,[^.]表示除了.这个字符以外所有的字符,这里.只代表自身符号。需要注意,%S代表%s的补集,等效于[^%s],%P代表%p的补集,另外适用的还有%A %C %D %G %L %U %W %X.
  • ^&,这两个字符是位置修饰符,如果^为pattern的第一个字符,代表字符串必须从开头进行匹配,如果$为pattern的最后一个字符,代表字符串必须匹配到最末尾,如果开头有^结尾有$,则字符串必须进行完整匹配。
  • +-*? 这四个字符为重复匹配修饰符。说明如下:
字符 说明
  • | 重复1次或者多次 贪婪匹配
  • | 重复0次或者多次 懒惰匹配
  • | 重复0次或者多次 贪婪匹配
    ?| 重复0次或者1次 贪婪匹配

贪婪匹配就是尽可能匹配的多,懒惰匹配就是尽可能匹配的少。

介绍完魔法字符,我们进行实际应用。

  1. 匹配lua所有合法变量名称,变量名称必须以字母或者下划线开头,名称可以包含字母数字下划线。(不排除关键字)

合法变量至少有一个字符,因此pattern值为[_%a][_%w]*,字符集[_%a]匹配字母和下划线,[_%w]*匹配0个或者多个字母数字下划线。

  1. 匹配字符串中的所有数值,包括带负号和正号的数值。

[+-]?%d+

  1. 匹配key = value形式,存入table

(%w+)%s*=%s*(%w+)

  1. 匹配[[]]或者[=[]=],包含n个等号格式的lua特殊表示字符串。

%[(=*)%[(.-)%]%1%]
5. utf8字符串反转。

utf8.charpattern

posted @ 2018-04-09 19:16  蓝萧  阅读(15244)  评论(0编辑  收藏  举报