Lua字符串模式和捕获 (转)
来源:互联网 发布:网络专卖授权书 编辑:程序博客网 时间:2024/06/05 11:53
已经拿Lua用了快两年的时间了,但是每次用到字符串的模式匹配的时候就总要去翻看Lua官网的说明,网上也没有一个比较详细的说明,也有好多朋友都向我询问这块的内容,其实这块的难点有三:
- 一个是对Lua的正则表达式不熟悉;
- 另一个是对Lua中string库提供的几个函数的用法不熟悉;
- 还有一点是Lua的string库提出了一个新的概念,叫做捕获,其实也不算什么新概念,只是和函数调用杂糅在一起行为就不好理解罢了。
这里我总结一下。
Lua内置字符串库用到模式的地方有4个函数,它们分别是:
string.find()
string.match()
string.gmatch()
string.gsub()
1、string.find(s, pattern, start, plain)
这个函数的功能是查找字符串 s 中的指定的模式 pattern。
local s =
"am+df"
print(string.find(s,
"m+"
, 1,
false
)) -- 2 2
print(string.find(s,
"m+"
, 1,
true
)) -- 2 3
其中字符 + 在 Lua 正则表达式中的意思是匹配在它之前的那个字符一次或者多次,也就是说m+ 在正则表达式里会去匹配 m, mm, mmm ……。所以当 string.find 第四个参数为 false 的时候,就只能在字符串 s 中找到m 这个字母是匹配的,那么返回的结果就是 2 2。
local s =
"am+df"
print(string.find(s,
"(m+)"
, 1,
false
)) -- 2 2 m
如果你想要捕获更多的内容,只需要用小括号把它括起来就好了,比如这样:
local s =
"am+df"
print(string.find(s,
"((m+))"
, 1,
false
)) -- 2 2 m m
print(string.find(s,
"(((m+)))"
, 1,
false
)) -- 2 2 m m m
关于捕获还有一点需要说明的,就是捕获只会在模式能够匹配成功的时候才会跟着 string 的函数进行返回,比如下面这个,我想捕获字母a ,但事实上这个模式根本无法匹配到,所以肯定是无法返回的:
local s =
"am+df"
print(string.find(s,
"(m+)(a)"
, 1,
false
)) -- nil
另外捕获返回的顺序,是依照左小括号的位置来定的,比如上面那个捕获了3个m 的例子,第一个m 其实是最外层的小括号捕获到的。为什么要提到捕获的顺序呢?因为我们可以使用%n 来取得第n个捕获的字符串,至于取得对应的捕获有什么用处呢?这个在后面会介绍到。
local s = ”am+df“
print(string.find(s,
"()(m+)()"
, 1,
false
)) -- 2 2 2 m 3
有一点也必须要提一下,就是在Lua5.1的源码当中,捕获字符串的数量是有限制的,默认是32个,也就是说你添加的小括号不能无限加,最多加32个。如果捕获超过限制,当然会报错了,比如:
local s = ”am+df“
print(string.find(s,
"()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()"
, 1,
false
)) -- 捕获33个
一般来说,对于使用,分析基本到此了,但是对于 Lua,因为源码简单,而且优美,又是拿C语言写的,心痒难耐,必须要了解一下源码才解恨。
Lua内置库的加载方式就不说了,在各个大神的文章里都可以看到,我们直接来看 string.find() 这个函数,函数在 lstrlib.c 文件里:
static
int
str_find (lua_State *L) {
return
str_find_aux(L, 1);
}
static
int
str_find_aux (lua_State *L,
int
find) {
size_t
l1, l2;
const
char
*s = luaL_checklstring(L, 1, &l1);
const
char
*p = luaL_checklstring(L, 2, &l2);
ptrdiff_t
init = posrelat(luaL_optinteger(L, 3, 1), l1) - 1;
if
(init < 0) init = 0;
else
if
((
size_t
)(init) > l1) init = (
ptrdiff_t
)l1;
if
(find && (lua_toboolean(L, 4) ||
/* explicit request? */
strpbrk
(p, SPECIALS) == NULL)) {
/* or no special characters? */
/* do a plain search */
const
char
*s2 = lmemfind(s+init, l1-init, p, l2);
if
(s2) {
lua_pushinteger(L, s2-s+1);
lua_pushinteger(L, s2-s+l2);
return
2;
}
}
else
{
MatchState ms;
int
anchor = (*p ==
'^'
) ? (p++, 1) : 0;
const
char
*s1=s+init;
ms.L = L;
ms.src_init = s;
ms.src_end = s+l1;
do
{
const
char
*res;
ms.level = 0;
if
((res=match(&ms, s1, p)) != NULL) {
if
(find) {
lua_pushinteger(L, s1-s+1);
/* start */
lua_pushinteger(L, res-s);
/* end */
return
push_captures(&ms, NULL, 0) + 2;
}
else
return
push_captures(&ms, s1, res);
}
}
while
(s1++ < ms.src_end && !anchor);
}
lua_pushnil(L);
/* not found */
return
1;
}
这个函数初步看起来还是比较长的,但是仔细分析一下就发现其实是很简单的。前面那 6 行,就是接收前 3 个参数罢了,只不过处理了一下那个查找起始点参数,防止了超出字符串长度。最关键的地方就是紧接着的 if else 逻辑,find 是传进来的参数,对于 string.find 来说就是1,所以不用管它,认为它一直是真就 OK 了,既然提到这里了,那么是不是还有别的地方也会调用这个函数原型的,bingo!我们搜索一下就会发现,其实 string.match() 函数其实也是调用这个函数原型的,而它的 find 参数就是传递的 0 。哈哈,难道 string.match 函数其实跟 string.find 函数是一样的?
static
int
str_match (lua_State *L) {
return
str_find_aux(L, 0);
}
static
int
str_find (lua_State *L) {
return
str_find_aux(L, 1);
}
这个留到介绍 string.match 函数的时候再说。拉回来,继续谈这个 if else 逻辑,if 的判断条件其实就是看你调用 string.find 的第四个参数,如果第四个参数传递了 true,也就是我上面说的,不使用特殊字符模式,或者是模式中压根就没有特殊字符,那个SPECIALS 宏同样定义在这个文件中:
怀着一点小激动,我点开了 lmemfind() :
static
const
char
*lmemfind (
const
char
*s1,
size_t
l1,
const
char
*s2,
size_t
l2) {
if
(l2 == 0)
return
s1;
/* empty strings are everywhere */
else
if
(l2 > l1)
return
NULL;
/* avoids a negative `l1' */
else
{
const
char
*init;
/* to search for a `*s2' inside `s1' */
l2--;
/* 1st char will be checked by `memchr' */
l1 = l1-l2;
/* `s2' cannot be found after that */
while
(l1 > 0 && (init = (
const
char
*)
memchr
(s1, *s2, l1)) != NULL) {
init++;
/* 1st char is already checked */
if
(
memcmp
(init, s2+1, l2) == 0)
return
init-1;
else
{
/* correct `l1' and `s1' to try again */
l1 -= init-s1;
s1 = init;
}
}
return
NULL;
/* not found */
}
}
总的来说,这个比较的方法还是中规中矩的,从头开始查找匹配串的第一个字符,只不过用的是memchr 函数,找到了之后用 memcmp 函数来比较两个字符串是否是相同的,如果不相同就跳过检查了的字符继续。相比那些复杂的字符串匹配算法,这个既简单又可爱,赞一个:),memcmp 函数的执行自然比 str 系列的比较要快一些,因为不用一直检查 ‘\0’ 字符,关于memcmp 函数的做法,这里有一篇文章,虽然是说他的优化,但是看他的代码也能大致了解 memcmp 的做法:http://blog.chinaunix.net/uid-25627207-id-3556923.html
在介绍 string.find 函数的时候提到过,Lua 源码中 string.match 调用的函数其实跟 string.find 调用的函数是相同的,都是str_find_aux(lua_State *L, int find) 函数,唯一不同的地方在于 string.match 调用时 find 参数传递的是 0,这样就不会进入str_find_aux() 里简单匹配的分支,直接进行模式匹配。
s =
"hello world from Lua"
for
w
in
string.gmatch(s,
"%a+"
)
do
print(w)
end
--[[
hello
world
from
Lua
]]
至于 %a+ 的意义嘛,在 string.find() 的介绍里提到过字符+ 的用法,至于%a 嘛,它是匹配所有的字母。这里需要注意的是,字符串 s 里由 4 个单词,用 3 个空格进行了分隔,所以调用一次 string.gmatch(s, "%a+"),只会匹配 s 中的第一个单词,因为遇到空格匹配就失败了。
local s =
"am+df"
print(string.find(s,
"(m+)"
, 1,
false
)) -- 2 2 m
print(string.find(s,
"^(m+)"
, 1,
false
)) -- nil
第二个匹配,因为在模式前面增加了 ^ ,所以会从字符串 s 的最开始就进行匹配,也就是从字母a 开始匹配,a 当然无法和(m+) 匹配成功了,所以直接就返回 nil 了。这个处理在上面讲 string.find 的时候源码函数str_find_aux() 里 else 分支模式匹配里可以看到,有专门处理 ^ 字符的代码。
前面那两行是返回Lua迭代器所需求的状态和迭代函数,不用去管它。让我们来看一下gmatch_aux (lua_State *L) 函数,刨去为了迭代器做处理之后,就和 string.match() 函数实现没有什么区别了,最后都调用 match() 函数进行模式匹配。不同的地方就是上面说的字符 ^ 的处理这里是没有的。
local s =
"am+dmf"
print(string.gsub(s,
"()(m+)"
,
"%1"
)) -- a2+d5f 2
print(string.gsub(s,
"()(m+)"
,
"%2"
)) -- am+dmf 2
print(string.gsub(s,
"()(m+)"
,
"%3"
)) -- error: invalid capture index
local s =
"am+dmf"
print(string.gsub(s,
"()(m+)"
,
"%0%0%0"
)) -- ammm+dmmmf 2
匹配到的串是 m,用mmm 替换了原串中的 m。
你可能要问,既然 % 被单独处理了,那么我想要用% 去替换怎么办,只需要用%% 就可以表示% 自身了。比如:
local s =
"am+dmf"
print(string.gsub(s,
"()(m+)"
,
"%%"
)) -- a%+d%f 2
当rep是一个table的时候,每次匹配到了之后,都会用第一个捕获作为key去查询这个table,然后用table的内容来替换匹配串,如果没有指定捕获,那么,就用整个匹配串作为key去查询,如果没有查到对应key的值,或者对应的值不是字符串和数字,那么就不做替换:
local s =
"am+dmf"
local t1 = {
[2] =
"hh"
,
[5] =
"xx"
,
}
local t2 = {}
print(string.gsub(s,
"()(m+)"
, t1)) -- ahh+dxxf 2
print(string.gsub(s,
"()(m+)"
, t2)) -- am+dmf 2
local t3 = {
[2] =
false
}
print(string.gsub(s,
"()(m+)"
, t3)) -- am+dmf 2
local t4 = {
[2] = { 123 }
}
print(string.gsub(s,
"()(m+)"
, t4)) -- error : invalid replacement value ( a table )
当rep是一个函数的时候,每当匹配到字符串的时候,就把模式所有的捕获按照捕获顺序作为参数传递给这个函数,如果没有指定捕获,则传递整个匹配的字符串给函数,函数的返回值如果是字符串或者是数字就替换掉匹配,如果不是则不做替换:
local s =
"am+dmf"
function
f1(...)
print(...) -- 2 m -- 5 m
return
"hh"
end
function
f2()
return
{ 123 }
end
print(string.gsub(s,
"()(m+)"
, f1)) -- ahh+dhhf 2
print(string.gsub(s,
"()(m+)"
, f2)) -- error : invalid replacement value ( a table )
第四个参数,用来表明,需要替换到第几个匹配为止,比如:
local s =
"am+dmf"
print(string.gsub(s,
"()(m+)"
,
"%%"
, -1)) -- am+dmf 0
print(string.gsub(s,
"()(m+)"
,
"%%"
, 0)) -- am+dmf 0
print(string.gsub(s,
"()(m+)"
,
"%%"
, 1)) -- a%+dmf 1
print(string.gsub(s,
"()(m+)"
,
"%%"
, 2)) -- a%+d%f 2
print(string.gsub(s,
"()(m+)"
,
"%%"
, 3)) -- a%+d%f 2
依然来看看源码是怎么写的:
static
int
str_gsub (lua_State *L) {
size_t
srcl;
const
char
*src = luaL_checklstring(L, 1, &srcl);
const
char
*p = luaL_checkstring(L, 2);
int
max_s = luaL_optint(L, 4, srcl+1);
int
anchor = (*p ==
'^'
) ? (p++, 1) : 0;
int
n = 0;
MatchState ms;
luaL_Buffer b;
luaL_buffinit(L, &b);
ms.L = L;
ms.src_init = src;
ms.src_end = src+srcl;
while
(n < max_s) {
const
char
*e;
ms.level = 0;
e = match(&ms, src, p);
if
(e) {
n++;
add_value(&ms, &b, src, e);
}
if
(e && e>src)
/* non empty match? */
src = e;
/* skip it */
else
if
(src < ms.src_end)
luaL_addchar(&b, *src++);
else
break
;
if
(anchor)
break
;
}
luaL_addlstring(&b, src, ms.src_end-src);
luaL_pushresult(&b);
lua_pushinteger(L, n);
/* number of substitutions */
return
2;
}
可以看到它处理了符号 ^ ,循环进行匹配,如果匹配到了,就按照不同的类型把替换串添加进结果里,最后把所有字符压回栈上。
总的来说 string.gsub() 函数实现的效果跟我们一般意义上的替换是相同的,你可能会纳闷为什么它不叫 string.greplace ,其实我也纳闷。
上面介绍完了 4 个用到了模式的函数之后,我们再来看看Lua的模式有什么奇妙之处。
模式
让我们来看看,都有哪些特殊字符需要解释,其实这一部分在Lua的官方文档中,介绍的还是很清楚的:
首先,任何单独的字符,除了上面那些特殊字符外,都代表他们本身。注意前提是他们独立出现。
其次,Lua定义了一些集合,它们分别如下:
. :代表任意的字符。
%a :代表任意字母。
%c :代表任意控制字符。
%d :代表任意数字。
%l :代表任意小写字母。
%p :代表任意标点符号。
%s :代表任意空白字符(比如空格,tab啊)。
%u :代表任意大写字母。
%w :代表任意字母和数字。
%x :代表任意16进制数字。
%z :代表任意跟0相等的字符。
%后面跟任意一个非字母和数字的字符,都代表了这个字符本身,包括上面那些特殊字符以及任何标点符号都可以用这个方式来表达。
[set]:代表一个自定义的字符集合。你可以使用符号- 来标识一个范围,比如 1-9,a-z 之类的。需要注意的是,上面提到的那些字符集合也可以在这个自定义的集合里用,但是你不能这么写[%a-z],这样的集合是没有意义的。
[^set]:代表字符集合[set]的补集(补集是什么意思,我了个去,问你数学老师去)。
另外,对于上面提到的所有用 % 跟一个字母组成的集合,如果把字母大写,那么就对应那个集合的补集,比如%S 的意思就是所有非空白字符。Lua官网还强调了一下,这里个定义跟本地的字符集有关,比如集合 [a-z] 就不一定跟 %l 是相等的。
任意一个单字符表达的集合,包括 % 加单字符表达的集合后面都可以跟4种符号,他们分别是* 、+ 、- 、?。
* :意思是前面的集合匹配0个或者更多字符,并且是尽量多的匹配。
+ :意思是前面的集合匹配1个或者更多字符。
- :意思是前面的集合匹配0个或者更多字符,尽量少的匹配。
? :意思是前面的集合匹配0个或者1个。
如下:
local a =
"ammmf"
print(string.match(a,
"%a"
)) -- a
print(string.match(a,
"%a*"
)) -- ammmf
print(string.match(a,
"%a+"
)) -- ammmf
print(string.match(a,
"%a-"
)) --
print(string.match(a,
"%a?"
)) -- a
看了上面的例子,你可能会想,那 * 和+ 或者加不加? 有什么区别呢?是有区别的,因为匹配0个和匹配1个有的时候就是有没有匹配成功的关键,比如加上? 就可以匹配0个,意味着即使没有对应集合的内容,也算匹配成功了,如果有捕获的话,这个时候捕获是生效的。比如:
local a =
"ammmf"
print(string.match(a,
"()c"
)) -- nil
print(string.match(a,
"()c?"
)) -- 1
如果你现在还不知道 string.match() 是什么意思,就翻到前面去看吧。
还有一个特殊的字符需要介绍,就是 %b 后面跟两个不同的字符xy,它的意思是匹配从x开始,到y结束的字符串,而且要求这个字符串里x和y的数量要相同。比如%b() 就是匹配正常的小括号,如下:
local a =
"aaabb"
print(string.match(a,
"%bab"
)) -- aabb
最后,我在介绍 string.gmatch 的时候介绍过字符^ 的用法,它放在模式的首部,意思是从原串的首部就开始匹配,这里还有一个特殊字符跟它的用法类似,它就是$ 字符,这个字符放在模式的末尾,意思是从原串的尾部开始匹配。在其他位置就跟^ 一样,也没有意义。
捕获
捕获的意思在介绍 string.find 的时候已经详细介绍过了,这里再提一笔,捕获是在模式中,用小括号括起来的子模式,它在匹配发生的时候截取小括号内模式匹配到的字符串,然后保存下来,默认最多保存 32 个,可以在Lua源码中修改保存的数量。另外捕获的顺序是按照小括号左括号的位置来定的。至于捕获如何使用,请参看我上面介绍的4个使用了模式的函数的具体用法。
本文出自 “菜鸟浮出水” 博客,请务必保留此出处http://rangercyh.blog.51cto.com/1444712/1393067
- Lua字符串模式和捕获 (转)
- Lua string(字符串)和强大的模式匹配功能
- lua字符串模式匹配
- lua 字符串模式匹配
- lua 字符串模式匹配
- lua中的捕获(capture)
- lua 二进制和八进制字符串互转
- lua之字符串模式匹配
- (转)Lua中的字符串函数库
- Lua字符串(string)
- Lua 中的捕获
- Lua中的捕获
- Lua中的捕获
- Lua字符串模式匹配函数小结
- [转]Lua模式匹配
- (转)Lua模式匹配
- lua 与 c/c++ 交互(6) lua调用C++(使用数组 和字符串函数)
- Lua教程(五):C/C++操作Lua数组和字符串示例
- 关于cocos2d-x对etc1图片支持的分析
- 并查集 小希的迷宫
- bzoj 1053 题解
- Android Studio Tips Of the Day - Roundup #3
- Demo2 模拟简单登陆 服务器端代码
- Lua字符串模式和捕获 (转)
- 【线性同余方程】toj2297&poj2115 C Looooops
- 数学建模总结(一)
- C#如何读写xml文件
- Java 8新特性:lambda表达式
- Kettle的第二个实践--数据获取并转换
- MFC小笔记之文件
- [Java]Android发送UDP数据包
- JSP页面UTF-8格式中文字符串乱码问题解决方法