模式匹配概述
Lua 提供了内置的模式匹配功能,虽然不如正则表达式强大,但对大多数文本处理场景已经足够,而且性能优异。模式匹配主要通过四个字符串函数实现:
string.find(s, pattern [, init [, plain]]) -- 查找第一个匹配
string.match(s, pattern [, init]) -- 返回捕获内容
string.gmatch(s, pattern) -- 返回所有匹配的迭代器
string.gsub(s, pattern, repl [, n]) -- 全局替换
字符类
字符类是模式匹配的基础,用于匹配一类字符:
| 字符类 | 匹配内容 | 等价写法 |
|---|---|---|
%a | 字母 | [A-Za-z] |
%d | 数字 | [0-9] |
%l | 小写字母 | [a-z] |
%u | 大写字母 | [A-Z] |
%w | 字母和数字 | [A-Za-z0-9] |
%s | 空白字符 | [\t\n\r\f\v ] |
%p | 标点符号 | |
%c | 控制字符 | |
. | 任意字符 |
大写版本表示补集:%A 匹配非字母,%D 匹配非数字,以此类推。
-- 示例
print(string.match("Hello123", "%a+")) -- Hello(匹配字母)
print(string.match("Hello123", "%d+")) -- 123(匹配数字)
print(string.match("Hello123", "%w+")) -- Hello123(匹配字母数字)
print(string.match(" abc ", "%S+")) -- abc(匹配非空白)
自定义字符集
使用方括号可以定义自定义字符集:
-- 匹配元音字母
print(string.match("hello", "[aeiou]")) -- e
-- 匹配十六进制数字
print(string.match("0xFF", "[0-9A-Fa-f]+")) -- 0xFF
-- 范围表示
print(string.match("abc123", "[a-z]+")) -- abc
-- 补集(以 ^ 开头)
print(string.match("abc123", "[^0-9]+")) -- abc
-- 特殊字符在字符集中不需要转义
print(string.match("a.b", "[.]")) -- .
量词
量词控制匹配次数:
| 量词 | 含义 | 说明 |
|---|---|---|
* | 0 次或多次 | 贪婪匹配 |
+ | 1 次或多次 | 贪婪匹配 |
- | 0 次或多次 | 非贪婪匹配 |
? | 0 次或 1 次 | 可选匹配 |
-- * 贪婪匹配:尽可能多
print(string.match("<tag>content</tag>", "<.*>"))
-- 输出: <tag>content</tag>
-- - 非贪婪匹配:尽可能少
print(string.match("<tag>content</tag>", "<.->"))
-- 输出: <tag>
-- + 至少一次
print(string.match("abc123", "%a+")) -- abc
print(string.match("123", "%a+")) -- nil(没有字母)
-- ? 可选
print(string.match("color", "colou?r")) -- color
print(string.match("colour", "colou?r")) -- colour
捕获
捕获用括号 () 包围,可以提取匹配的子串:
-- 基本捕获
local date = "2025-06-15"
local y, m, d = string.match(date, "(%d+)-(%d+)-(%d+)")
print(y, m, d) -- 2025 06 15
-- 捕获位置
local s = "name=John, age=30"
local key, value
for k, v in string.gmatch(s, "(%w+)=(%w+)") do
print(k, v)
end
-- name John
-- age 30
-- 嵌套捕获
local html = '<a href="url">text</a>'
local tag, attr, content = string.match(html, '<(%a+)(.-)>(.-)</%1>')
print(tag, attr, content) -- a href="url" text
位置捕获
() 不带内容时捕获当前位置:
local s = "hello world"
local start_pos, end_pos = string.find(s, "world")
print(start_pos, end_pos) -- 7 11
-- 用位置捕获记录匹配位置
local word, pos = string.match(s, "(%w+)%s+()")
print(word, pos) -- hello 7
平衡匹配 %b
%bxy 匹配成对的字符 x 和 y,支持嵌套:
-- 匹配括号对
local s = "func(arg1, func2(inner))"
print(string.match(s, "%b()")) -- (arg1, func2(inner))
-- 匹配引号对
local q = 'say "hello world"'
print(string.match(q, '%b""')) -- "hello world"
-- 匹配花括号
local json = '{name: {first: "John"}}'
print(string.match(json, "%b{}")) -- {name: {first: "John"}}
最长匹配 %f
%f[set] 匹配从非 set 字符到 set 字符的前沿位置:
-- 匹配完整单词
local s = "the anthem"
print(string.gsub(s, "%f[%a]the%f[%A]", "THE"))
-- 输出: THE anthem 1
-- 只替换独立的 "the",不影响 "anthem" 中的 "the"
-- 匹配标识符开头
local code = "local x = myvar + 1"
for word in string.gmatch(code, "%f[%a_][%a_][%w_]*") do
print(word)
end
-- local
-- x
-- myvar
gsub 的高级用法
string.gsub 的替换参数可以是字符串、表或函数:
-- 字符串替换(%1, %2 引用捕获)
print(string.gsub("John Smith", "(%w+) (%w+)", "%2, %1"))
-- Smith, John
-- 表替换(用捕获内容作为键查找)
local env = {HOME = "/home/user", PATH = "/usr/bin"}
local s = "cd $HOME; ls $PATH"
local result = string.gsub(s, "$(%w+)", env)
print(result) -- cd /home/user; ls /usr/bin
-- 函数替换
local s = "hello 123 world 456"
local result = string.gsub(s, "%d+", function(n)
return tonumber(n) * 2
end)
print(result) -- hello 246 world 912
常见实战场景
解析 URL 参数
function parse_query(query)
local params = {}
for key, value in string.gmatch(query, "([^&=]+)=([^&=]+)") do
-- URL 解码
key = string.gsub(key, "%%(%x%x)", function(hex)
return string.char(tonumber(hex, 16))
end)
value = string.gsub(value, "%%(%x%x)", function(hex)
return string.char(tonumber(hex, 16))
end)
params[key] = value
end
return params
end
local params = parse_query("name=%E5%BC%A0%E4%B8%89&age=25&page=1")
for k, v in pairs(params) do
print(k, v)
end
模板变量替换
function render_template(template, vars)
return (string.gsub(template, "{{(%w+)}}", function(key)
return vars[key] or "{{" .. key .. "}}"
end))
end
local tmpl = "你好 {{name}},你的订单 {{order_id}} 已发货。"
local result = render_template(tmpl, {
name = "张三",
order_id = "ORD-20250615-001"
})
print(result)
-- 你好 张三,你的订单 ORD-20250615-001 已发货。
CSV 解析
function parse_csv_line(line)
local fields = {}
-- 处理带引号的字段
for field in string.gmatch(line, '"([^"]*)"([^,]*),?') do
table.insert(fields, field)
end
-- 如果简单 gmatch 不行,用 gsub 逐字段处理
local pos = 1
fields = {}
while pos <= #line do
if string.sub(line, pos, pos) == '"' then
-- 带引号字段
local field, next_pos = string.match(
line, '^"([^"]*)",?()', pos)
if field then
table.insert(fields, field)
pos = next_pos
else
break
end
else
-- 不带引号字段
local field, next_pos = string.match(
line, '^([^,]*),?()', pos)
table.insert(fields, field)
pos = next_pos
if next_pos > #line then break end
end
end
return fields
end
print(table.concat(parse_csv_line('hello,world,test'), " | "))
-- hello | world | test
驼峰与蛇形命名转换
-- 驼峰转蛇形
function camel_to_snake(s)
return (string.gsub(s, "([A-Z])", function(c)
return "_" .. c:lower()
end):gsub("^_", ""))
end
-- 蛇形转驼峰
function snake_to_camel(s)
return (string.gsub(s, "_(%l)", function(c)
return c:upper()
end))
end
print(camel_to_snake("getUserName")) -- get_user_name
print(snake_to_camel("get_user_name")) -- getUserName
简单 JSON 值解析
function parse_json_value(s)
s = string.match(s, "^%s*(.-)%s*$") -- trim
if s == "null" then return nil end
if s == "true" then return true end
if s == "false" then return false end
local num = tonumber(s)
if num then return num end
-- 字符串
local str = string.match(s, '^"(.*)"$')
if str then
str = string.gsub(str, "\\(.)", {
['"'] = '"', ['\\'] = '\\',
['n'] = '\n', ['t'] = '\t', ['r'] = '\r'
})
return str
end
return s
end
print(parse_json_value('"hello"')) -- hello
print(parse_json_value("42")) -- 42
print(parse_json_value("true")) -- true
print(parse_json_value("null")) -- nil
注意事项
使用 Lua 模式匹配需要注意以下要点:
- Lua 模式不是正则表达式,不支持
|(或)、(?:...)(非捕获组)等特性 - 特殊字符
-.*+?^$()[]%需要转义才能作为字面量匹配 string.gsub返回两个值:替换后的字符串和替换次数,注意用括号丢弃第二个值- 模式匹配在 UTF-8 环境下按字节工作,不感知多字节字符
- 对于复杂的文本处理需求,可以考虑使用 LPeg 库
string.find的第四个参数plain=true会禁用模式匹配,进行纯文本查找
继续阅读
探索更多技术文章
浏览归档,发现更多关于系统设计、工具链和工程实践的内容。