Lua String Patterns
Pattern Classes
Character classes (not regex — Lua patterns)
-- Lua patterns are NOT regular expressions
-- No alternation (|), no grouping for repetition, no backreferences
-- Character classes:
-- . any character
-- %a letters %A non-letters
-- %d digits %D non-digits
-- %l lowercase %L non-lowercase
-- %u uppercase %U non-uppercase
-- %w alphanumeric %W non-alphanumeric
-- %s whitespace %S non-whitespace
-- %p punctuation %P non-punctuation
-- %c control chars %C non-control
-- Quantifiers:
-- * zero or more (greedy)
-- + one or more (greedy)
-- - zero or more (lazy)
-- ? zero or one
string.find and string.match
Finding and extracting
local log = "2026-04-10 14:30:22 ERROR auth failed from 10.50.1.99"
-- find returns start, end positions
local s, e = string.find(log, "ERROR")
print(s, e) -- 21, 25
-- find with captures
local ip_start, ip_end, ip = string.find(log, "(%d+%.%d+%.%d+%.%d+)")
print(ip) -- 10.50.1.99
-- match returns captures only
local date = string.match(log, "(%d+%-%d+%-%d+)")
print(date) -- 2026-04-10
-- Multiple captures
local year, month, day = string.match(log, "(%d+)-(%d+)-(%d+)")
print(year, month, day) -- 2026 04 10
-- Plain text search (no pattern interpretation)
string.find(log, "10.50", 1, true) -- plain flag = 4th arg
string.gmatch
Iterator for all matches
-- Extract all words
for word in string.gmatch(log, "%S+") do
print(word)
end
-- Extract all IPs from multi-line text
local text = [[
server 10.50.1.1
dns 10.50.1.50
vault 10.50.1.40
]]
local ips = {}
for ip in text:gmatch("(%d+%.%d+%.%d+%.%d+)") do
ips[#ips + 1] = ip
end
-- Parse key=value pairs
local config_line = "timeout=30 retries=3 verbose=true"
local config = {}
for k, v in config_line:gmatch("(%w+)=(%w+)") do
config[k] = v
end
string.gsub
Search and replace
-- Simple replacement
local clean = log:gsub("ERROR", "WARN")
-- Replace with limit
local first_only = log:gsub("%d", "X", 4) -- replace first 4 digits
-- Replace with capture reference
local redacted = log:gsub("%d+%.%d+%.%d+%.%d+", "<REDACTED>")
-- Replace with function
local result = log:gsub("%d+", function(n)
return tostring(tonumber(n) * 2)
end)
-- Replace with table lookup
local level_map = { ERROR = "ERR", WARNING = "WARN", INFO = "INF" }
local short = log:gsub("%u+", level_map)
-- gsub returns string AND replacement count
local output, count = log:gsub("%d", "X")
print(count) -- number of replacements
Practical Patterns
Infrastructure parsing
-- Parse syslog-style line
local function parse_syslog(line)
local date, time, level, msg = line:match(
"^(%d+%-%d+%-%d+)%s+(%d+:%d+:%d+)%s+(%u+)%s+(.+)$"
)
return { date = date, time = time, level = level, message = msg }
end
-- Validate IP address
local function is_valid_ip(s)
local a, b, c, d = s:match("^(%d+)%.(%d+)%.(%d+)%.(%d+)$")
if not a then return false end
a, b, c, d = tonumber(a), tonumber(b), tonumber(c), tonumber(d)
return a <= 255 and b <= 255 and c <= 255 and d <= 255
end
-- Split string (Lua has no built-in split)
local function split(s, sep)
local parts = {}
for part in s:gmatch("([^" .. sep .. "]+)") do
parts[#parts + 1] = part
end
return parts
end
local fqdn_parts = split("vault-01.inside.domusdigitalis.dev", ".")