Lua字符串模式和捕获

分类:CentOS运维 阅读:55136 次

每次用到字符串的模式匹配的时候就总要去翻看Lua官网的说明,网上也没有一个比较详细的说明,也有好多朋友都向我询问这块的内容,其实这块的难点有三:

1、string.find(s, pattern, start, plain)

这个函数的功能是查找字符串 s 中的指定的模式 pattern。

其中字符+在 Lua 正则表达式中的意思是匹配在它之前的那个字符一次或者多次,也就是说m+在正则表达式里会去匹配 m, mm, mmm ……。所以当 string.find 第四个参数为 false 的时候,就只能在字符串 s 中找到m这个字母是匹配的,那么返回的结果就是 2 2。

如果你想要捕获更多的内容,只需要用小括号把它括起来就好了,比如这样:

另外捕获返回的顺序,是依照左小括号的位置来定的,比如上面那个捕获了3个m的例子,第一个m其实是最外层的小括号捕获到的。为什么要提到捕获的顺序呢?因为我们可以使用%n来取得第n个捕获的字符串,至于取得对应的捕获有什么用处呢?这个在后面会介绍到。

有一点也必须要提一下,就是在Lua5.1的源码当中,捕获字符串的数量是有限制的,默认是32个,也就是说你添加的小括号不能无限加,最多加32个。如果捕获超过限制,当然会报错了,比如:

一般来说,对于使用,分析基本到此了,但是对于 Lua,因为源码简单,而且优美,又是拿C语言写的,心痒难耐,必须要了解一下源码才解恨。

Lua内置库的加载方式就不说了,在各个大神的文章里都可以看到,我们直接来看 string.find() 这个函数,函数在 lstrlib.c 文件里:

这个函数初步看起来还是比较长的,但是仔细分析一下就发现其实是很简单的。前面那 6 行,就是接收前 3 个参数罢了,只不过处理了一下那个查找起始点参数,防止了超出字符串长度。最关键的地方就是紧接着的 if else 逻辑,find 是传进来的参数,对于 string.find 来说就是1,所以不用管它,认为它一直是真就 OK 了,既然提到这里了,那么是不是还有别的地方也会调用这个函数原型的,bingo!我们搜索一下就会发现,其实 string.match() 函数其实也是调用这个函数原型的,而它的 find 参数就是传递的 0 。哈哈,难道 string.match 函数其实跟 string.find 函数是一样的?

这个留到介绍 string.match 函数的时候再说。拉回来,继续谈这个 if else 逻辑,if 的判断条件其实就是看你调用 string.find 的第四个参数,如果第四个参数传递了 true,也就是我上面说的,不使用特殊字符模式,或者是模式中压根就没有特殊字符,那个SPECIALS宏同样定义在这个文件中:

Lua字符串模式和捕获

总的来说,这个比较的方法还是中规中矩的,从头开始查找匹配串的第一个字符,只不过用的是memchr函数,找到了之后用memcmp函数来比较两个字符串是否是相同的,如果不相同就跳过检查了的字符继续。相比那些复杂的字符串匹配算法,这个既简单又可爱,赞一个:),memcmp函数的执行自然比 str 系列的比较要快一些,因为不用一直检查 ‘\0’ 字符,关于memcmp函数的做法,这里有一篇文章,虽然是说他的优化,但是看他的代码也能大致了解memcmp的做法:http://blog.chinaunix.net/uid-25627207-id-3556923.html

至于%a+的意义嘛,在 string.find() 的介绍里提到过字符+的用法,至于%a嘛,它是匹配所有的字母。这里需要注意的是,字符串 s 里由 4 个单词,用 3 个空格进行了分隔,所以调用一次 string.gmatch(s, "%a+"),只会匹配 s 中的第一个单词,因为遇到空格匹配就失败了。

第二个匹配,因为在模式前面增加了^,所以会从字符串 s 的最开始就进行匹配,也就是从字母a开始匹配,a当然无法和(m+)匹配成功了,所以直接就返回 nil 了。这个处理在上面讲 string.find 的时候源码函数str_find_aux()里 else 分支模式匹配里可以看到,有专门处理^字符的代码。

匹配到的串是m,用mmm替换了原串中的m。

你可能要问,既然%被单独处理了,那么我想要用%去替换怎么办,只需要用%%就可以表示%自身了。比如:

当rep是一个table的时候,每次匹配到了之后,都会用第一个捕获作为key去查询这个table,然后用table的内容来替换匹配串,如果没有指定捕获,那么,就用整个匹配串作为key去查询,如果没有查到对应key的值,或者对应的值不是字符串和数字,那么就不做替换:

当rep是一个函数的时候,每当匹配到字符串的时候,就把模式所有的捕获按照捕获顺序作为参数传递给这个函数,如果没有指定捕获,则传递整个匹配的字符串给函数,函数的返回值如果是字符串或者是数字就替换掉匹配,如果不是则不做替换:

第四个参数,用来表明,需要替换到第几个匹配为止,比如:

依然来看看源码是怎么写的:

可以看到它处理了符号^,循环进行匹配,如果匹配到了,就按照不同的类型把替换串添加进结果里,最后把所有字符压回栈上。

总的来说 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个。

如下:

看了上面的例子,你可能会想,那*和+或者加不加?有什么区别呢?是有区别的,因为匹配0个和匹配1个有的时候就是有没有匹配成功的关键,比如加上?就可以匹配0个,意味着即使没有对应集合的内容,也算匹配成功了,如果有捕获的话,这个时候捕获是生效的。比如:

如果你现在还不知道 string.match() 是什么意思,就翻到前面去看吧。

还有一个特殊的字符需要介绍,就是%b后面跟两个不同的字符xy,它的意思是匹配从x开始,到y结束的字符串,而且要求这个字符串里x和y的数量要相同。比如%b()就是匹配正常的小括号,如下:

最后,我在介绍 string.gmatch 的时候介绍过字符^的用法,它放在模式的首部,意思是从原串的首部就开始匹配,这里还有一个特殊字符跟它的用法类似,它就是$字符,这个字符放在模式的末尾,意思是从原串的尾部开始匹配。在其他位置就跟^一样,也没有意义。

捕获

捕获的意思在介绍 string.find 的时候已经详细介绍过了,这里再提一笔,捕获是在模式中,用小括号括起来的子模式,它在匹配发生的时候截取小括号内模式匹配到的字符串,然后保存下来,默认最多保存 32 个,可以在Lua源码中修改保存的数量。另外捕获的顺序是按照小括号左括号的位置来定的。至于捕获如何使用,请参看我上面介绍的4个使用了模式的函数的具体用法。

本文出自 “菜鸟浮出水” 博客,请务必保留此出处http://rangercyh.blog.51cto.com/1444712/1393067