Lua字符串模式匹配详解

全面掌握Lua字符串模式匹配,包括字符类、量词、捕获和常见文本处理场景的实战技巧。

模式匹配概述

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 匹配成对的字符 xy,支持嵌套:

-- 匹配括号对
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 会禁用模式匹配,进行纯文本查找

继续阅读

探索更多技术文章

浏览归档,发现更多关于系统设计、工具链和工程实践的内容。

全部文章 返回首页