Lua正则表达式

5k 词

几乎所有的编程语言都有正则表达式的支持,Lua也不例外,但是Lua的正则表达式和其他编程语言不太一样,不是标准的通用正则表达式,Lua有自己的一套表示方式。

匹配模式

Lua的匹配模式主要是用于Lua字符串处理函数string.find, string.match,string.gmatch, string.gsub服务的。

字符类

字符 说明
x 表示字符x本身,x不能是魔法字符^ $ ( ) % . [ ] * + - ?
%x 表示字符x本身,x为所有非字母数字字符(包括魔法字符,标点符号等)
. 表示所有字符
%a 表示所有字母
%A %a的补集
%c 表示所有控制字符
%C %c的补集
%d 表示所有数字
%D %d的补集
%g 表示除空白字符以外的所有可打印字符
%G %g的补集
%l 表示所有小写字母
%L %l的补集
%p 表示所有标点符号
%P %p的补集
%s 表示所有空白字符
%S %s的补集
%u 表示所有大写字母
%U %u的补集
%w 表示所有字母及数字
%W %w的补集
%x 表示所有16进制数字符号
%X %x的补集
[set] 表示set中的字符组合, 可以使用-来表示范围,例如[0-7%l%-]表示8进制数字加小写字母与-字符。交叉使用类和范围的行为未定义,像[%a-z][a-%%]这样的模式串没有意义。
[^set] 表示set的补集
  • 如何定义字母、空格、或是其他字符组取决于当前的区域设置。特别注意:[a-z]未必等价于%l
  • 上述单个字符类匹配该类别中任意单个字符。

模式条目

字符 说明
+ 将匹配一或多个该类的字符,这个条目总是匹配尽可能长的串
* 将匹配零或多个该类的字符,这个条目总是匹配尽可能长的串
- 将匹配零或多个该类的字符,和*不同,这个条目总是匹配尽可能短的串
将匹配零或一个该类的字符,只要有可能,它会匹配一个
%n 将匹配一个等于第n号捕获物的子串,n可以从19
%bxy 将匹配以x开始y结束,且其中xy保持平衡的字符串, 例如%b(), %b[], %<>, %{}可以匹配到括号平衡的表达式
%f[set] 将匹配一个空串(边境模式),仅当后一个字符属于set,前一个字符不属于set。 注意单个字符也必须写在[]

模式

  • 模式指一个模式条目的序列。
  • 在模式最前面加上符号^将锚定从字符串的开始处做匹配。
  • 在模式最后面加上符号$将使匹配过程锚定到字符串的结尾。
  • 如果^$出现在其它位置,它们均没有特殊含义表示自身。

捕获

  • 模式可以在内部用小括号括起一个子模式,这些子模式被称为捕获物。当匹配成功时,由捕获物匹配到的子串被保存起来用于未来的用途。捕获物以它们左括号的次序来编号。
  • 作为一个特例,空的捕获()将捕获到当前字符串的索引位置(它是一个数字)。例如,如果将模式()aa() 作用到字符串flaaap上,将产生两个捕获物:35

string.find

  • 函数原型:string.find(s, pattern [, init [, plain]])
  • 查找字符串s中匹配到pattern的第一个字符串,若找到一个匹配,则返回匹配到的字符串在s中起始和结束位置的索引,没找到,则返回nil
  • 第三个参数init表示从哪里开始查找,默认值为1,可以为负值,表示从字符串末尾反向索引。
  • 第四个参数plain表示是否关闭pattern所表示的模式匹配,true表示关闭,默认是false表示开启模式匹配。
  • 如果在模式中定义了捕获,捕获到的若干值也会在两个索引之后返回。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22

local s = "Lua ^5.3.4$ was released on 30/01/2017"
print(string.find(s, "Lua")) -- 1 3
print(string.find(s, "Lua", 2)) -- nil
print(string.find(s, "Lua", -100)) -- 1 3

-- close the pattern
local i, j = string.find(s, "%d+/%d+/%d+")
print(i, j, "'" .. string.sub(s, i, j) .. "'") -- 29 38 '30/01/2017'
print(string.find(s, "%d+/%d+/%d+", 1, true)) -- nil

-- ^, $ position in a pattern, has different meaning
print(string.find(s, "%d+$")) -- 35 38
local i, j = string.find(s, "%d$.%a+")
print(i, j, "'" .. string.sub(s, i, j) .. "'") -- 10 15 '4$ was'
print(string.find(s, "^%a+")) -- 1 3
local i, j = string.find(s, ".^%d%.%d")
print(i, j, "'" .. string.sub(s, i, j) .. "'") -- 4 8 ' ^5.3'

-- captures
local i, j, cap1, cap2, cap3 = string.find(s, "(%d+)/(%d+)/(%d+)")
print(i, j, cap1, cap2, cap3) -- 29 38 30 01 2017

string.match

  • 函数原型:string.match(s, pattern [, init])

  • 查找字符串s中匹配到pattern的第一个字符串,若没找到,则返回nil,若找到了一个匹配:

    • 如果pattern不包含任何捕获,则返回整个匹配到的字符串

    • 如果pattern包含捕获,则只按顺序返回捕获的部分

  • 第三个参数init表示从哪里开始查找,默认值为1,可以为负值,表示从字符串末尾反向索引。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
-- string.match
local s = "Lua ^5.3.4$ was released on 30/01/2017"
print(string.match(s, "Lua")) -- Lua
print(string.match(s, "%d+/%d+/%d+")) -- 30/01/2017

-- pattern contains captures
print(string.match(s, "%d+/(%d+)/(%d+)")) -- 01 2017

-- quote match
local s = [[then he said: "it's all right"!]]
local q, quotedPart = string.match(s, "(["'])(.-)%1")
print(quotedPart) -- it's all right
print(q) -- "

-- %bxy pattern
local s = "a (enclosed (in) parentheses) line"
print(string.match(s, "%b()")) -- (enclosed (in) parentheses)

-- empty captures
print(string.match("hello", "()ll()")) -- 3 5

string.gmatch

  • 函数原型:string.gmatch(s, pattern)
  • 返回一个迭代器函数,每次调用这个函数都会返回s中匹配到pattern的下一个捕获。 如果pattern不包含任何捕获,则返回整个匹配到的字符串。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
-- string.gmatch
local s = "Lua ^5.3.4$ was released on 30/01/2017"
for w in string.gmatch(s, "%a+") do
print(string.upper(w))
end
--[[
LUA
WAS
RELEASED
ON
]]

-- pattern contains captures
local t = {}
local s = "from=world, to=Lua"
for k, v in string.gmatch(s, "(%w+)=(%w+)") do
t[k] = v
end

string.gsub

  • 函数原型:string.gsub(s, pattern, repl [, n])
  • 将字符串s中所有(或者前n个)匹配到pattern的内容都替换成repl指定的内容,并返回其副本。repl可以是字符串、表、或函数。gsub还会在第二个返回值中返回一共发生了多少次匹配。
  • 如果repl是一个字符串,那么把这个字符串作为替换品。字符%是一个转义符:repl中的所有形式为%d的串表示第d个捕获到的子串,d可以是19%0表示整个匹配。
  • 如果repl是张表,每次匹配时都会用第一个捕获物作为键去查这张表来获取要替换的字符串。
  • 如果repl是个函数,则在每次匹配发生时都会调用这个函数。所有捕获到的子串依次作为参数传入来获取要替换的字符串。
  • 如果表的查询结果或函数的返回结果是一个字符串或是个数字,都将其作为替换用串;而在返回falsenil时不作替换。
  • 任何情况下,pattern中没有设定捕获都看成是捕获整个pattern
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
-- string.gsub
-- %f[set], 2 times of replacement
local s = "the anthem is the theme"
print(string.gsub(s, "%f[%w]the%f[%W]", "one")) -- one anthem is one theme 2

-- %0, %1~9
local s = "Lua 5.3.4 was released on 30/01/2017"
local s1, n = string.gsub(s, "(%d+)/(%d+)/(%d+)", "%0 [%3-%2-%1]")
print(s1, n) -- Lua 5.3.4 was released on 30/01/2017 [2017-01-30] 1
print(string.gsub("hello world", "(%w+)", "%1 %1")) -- hello hello world world 2

-- only one time of replacement
print(string.gsub("hello world", "(%w+)", "%1 %1", 1)) -- hello hello world 1
print(string.gsub("hello world from Lua", "(%w+)%s*(%w+)", "%2 %1")) -- world hello Lua from 2

-- os.getenv
local str = string.gsub("HOME=$HOME, JAVA_HOME=$JAVA_HOME", "%$([%w_]+)", os.getenv)
print(str) -- HOME=/Users/jtcheng, JAVA_HOME=$JAVA_HOME

-- table t do not contains a key "TAR"
local t = {name="lua", version="5.3.4"}
local s2, n = string.gsub("$TAR -zxvf $name-$version.tar.gz", "%$(%w+)", t)
print(s2, n) -- $TAR -zxvf lua-5.3.4.tar.gz 3