6.4.1 - 模式匹配
模式 pattern 在Lua中由常规字符串描述,其被解释为模式以用于模式匹配函数 string.find、string.gmatch、string.gsub、以及string.match中。本节会讲述相关语法以及这些字符串的含义。
字符类:
用以表示一类字符。描述各类字符时,可以使用以下字符的组合:
- X: (此处的 x 不是魔法字符 magic characters ^$()%.[]*+-? 中的任意一个)表示字符 x 本身。
- .: 表示任意字符。
- %a: 表示任意字母。
- %c: 表示任意控制字符。
- %d: 表示任意单个数字(0-9)。
- %g: 表示出了空白符以外的任意可打印字符。
- %l: 表示任意小写字母。
- %p: 表示任意的标点符号。
- %s: 表示任意的空白字符。
- %u: 表示任意大写字母
- %w: 表示任意的字母或数字
- %x: 表示十六进制数字符号。
- %x: (此处的 x 为任意非字母、非数字的字符)表示字符 x 。这是转义魔法字符 magic characters 的标准方式。所有非字母、非数字的字符 (包括所有标点,也包括非魔法字符) 都可以在匹配模式串中使用 '%' 前缀以表示自身。
- [set]: 表示包含在 set 中的字符集。可以使用按照字符值升序排列的两个字符来表示一个范围,两个字符使用 '-' 连接。上边说的 %x 形式表示的特殊字符也可以用在这里。其他在 set 中的字符则表示它们本身。例如,[%w_] (或者 [_%w]) 表示一个字母或数字加上一个下划线。[0-7] 表示0到7之间的数字,[0-7%l%-] 则表示一个0到7之间的数字加上一个小写字母以及一个连字符。
方括号可以直接放在 set 中的第一个位置,连字符可以直接放在开始或结束的位置,就可以表示它们自身(当然使用转义也可以)。
将范围表示和类表示混合使用的行为是未定义的,l类似 [%a-z] 或者 [a-%%] 的模式串是没有意义的。 - [^set]: 表示 set 的补集,关于 set 可以参见上边的说明。
对于所有表示单个字母的字符类表示(例如 %a、%c 等),其对应的大写形式表示了它们的补集。例如,%S 表示任意非空字符。
对于字母、空格以及其他字符组的定义取决于当前的区域设置。例如 [a-z] 可能并不等效于 %l 。
模式项:
一个模式项 pattern item 可以是:
- 单个字符类,将匹配该类中的任何单个字符。
- 单个字符类后跟一个 '*',将匹配该类中的零个或多个字符。该重复项始终会匹配尽可能长的串。
- 单个字符类后跟一个 '+',将匹配该类中的一个或多个字符。该重复项始终会匹配尽可能长的串。
- 单个字符类后跟一个 '-',也将匹配该类中的零个或多个字符。但不同于 '*' 的是,其始终匹配尽可能短的串。
- 单个字符类后跟一个 '?',将匹配零个或一个遇到该类中的字符。它尽可能地只匹配一个。
- %n ,此处 n 为1到9之间的字符;该项匹配一个等于第 n 个匹配项的子串(见下文)。
- %bxy ,此处的 x 和 y 分别是两个确实的字符,会匹配以 x 开始、以 y 为结尾的项,并且这里的 x 和 y 是平衡的。意思是从左往右读取字符串,如果遇到 x 就计数+1,遇到 y 就计数-1,末尾的 y 就是计数到0的那个位置的 y 。例如:%b() 模式项会匹配成对的括号。
- %f[set] ,前向匹配的模式;将匹配位于 set 中的某个字符之前的空串,并且其之前的字符不在 set 之中。这里的 set 集合的含义同之前的描述。所匹配到的空串的开始和结束处应当做 '\0' 处理。
模式:
一个模式 pattern 由一系列模式项组成。在模式开始处的 '^' 符会锚定在子串开始处匹配。在模式末尾处的 '$' 符会锚定在子串末尾处匹配。其他位置处的 '^' 和 '$' 符没有什么特殊含义而只是表示它们本身。
捕获:
模式中可以包含由括号包围起来的子模式;该子模式被称为捕获 captures 。每次成功匹配时,匹配到的子串中对应子模式的部分会被保存(即捕获到了)下来以供之后使用。多个捕获项之间是依据最左括号的顺序排列编号的。例如对于模式 "(a*(.)%w(%s*))" ,首先其最外层(最左边)括号中的部分 "a*(.)%w(%s*)" 被作为第一个捕获项,并且其编号为1;然后再匹配下一个括号中的 "." 作为2号捕获项;最后的括号中的 "%s*" 作为3号捕获项。
有个特例的捕获,() 会捕获当前字符串所在的位置(是个 number)(原文的描述不准确,应当是每个空括号紧跟的字符所在的位置,末尾的空括号就表示匹配到的子串后边的那个字符的位置)。例如,如果我们对字符串 "flaaap" 做捕获 "()aa()" ,那么其会产生两个捕获项:3和5(第一个 a 在3号位置,匹配到的 "aa" 下一个字符位置是5,虽然下一个还是 a ,但是无关紧要,此处举的例子很有误导性)。
多重匹配:
函数string.gsub和迭代器string.gmatch会在子串中多次匹配所出的模式。对于这些函数,每个新有效匹配的末尾至少会在之前匹配的末尾位置后隔一个字节。换言之,模式匹配机制永远不会接受空字符串作为紧跟着另一个匹配的下一个匹配(原文中的“immediately after”可以指紧接着的下一个或者是末尾处跟着的,还是不准确。此处应当是指模式匹配机制中一个匹配项的下一个匹配不可能是在其末尾同位置的空字符串)。可以观察以下代码示例:
string.gsub("abc", "()a*()", print);
--> 1 2
--> 3 3
--> 4 4
第二个和第三个结果可以看出,虽然都是匹配的空串,但是其位置仍然不会和上一个匹配的末尾位置重合,只有这样迭代器才会有尽头,而不会是死循环。