String patterns
Lesson 8.3
Patterns are a small language for searching text. With them you can check input, pull numbers out of a command, split a line into words, and replace text.
Where patterns are used
Four string functions take a pattern:
| Function | Does |
|---|---|
s:find(p) |
the start and end position of the first match |
s:match(p) |
the text of the first match (or its captures) |
s:gmatch(p) |
an iterator over every match, for a for loop |
s:gsub(p, repl) |
a copy with every match replaced, plus the number of replacements |
In a pattern, most characters match themselves. ("diamond sword"):find("sword") finds the word sword. A few characters are special, and they make patterns powerful.
Not regular expressions
Lua patterns look like the regular expressions of other languages, but they are simpler and different. There is no | for “or”, no {2,3} counts, and the escape character is %, not \.
Character classes
A class matches one character of a kind:
| Class | Matches |
|---|---|
. |
any character |
%a |
a letter |
%d |
a digit |
%l / %u |
a lowercase / uppercase letter |
%s |
a space, tab or line break |
%w |
a letter or digit |
%p |
punctuation |
%x |
a hexadecimal digit |
An uppercase version means the opposite: %D is any non-digit, %S any non-space.
print(("Level 42"):match("%d")) --> 4
print(("a1b2"):gsub("%d", "#")) --> a#b# 2
Sets
Square brackets build your own class. [abc] matches a, b or c. [a-z] is a range. [^...] means “anything except”:
print(("x=10;y=20"):gsub("[;=]", " ")) --> x 10 y 20 3
print(("minecraft:oak_log"):match("[^:]+$")) --> oak_log
You can put classes inside a set: [%w_] matches a letter, digit or underscore, the characters of a Minecraft ID path.
Repetition
These follow a class and say how many times it may repeat:
| Symbol | Means |
|---|---|
+ |
1 or more, as many as possible |
* |
0 or more, as many as possible |
- |
0 or more, as few as possible |
? |
0 or 1 (optional) |
print(("Level 42"):match("%d+")) --> 42
print((" hi "):match("%s*(.-)%s*$")) --> hi
print(("-5"):match("^-?%d+$")) --> -5
The difference between * and - matters with .: "<b>x</b>" matched with <.*> takes everything from the first < to the last >. <.-> stops at the first >.
print(("<b>x</b>"):match("<.*>")) --> <b>x</b>
print(("<b>x</b>"):match("<.->")) --> <b>
Anchors
^ at the start of a pattern means “must match at the beginning”. $ at the end means “must match at the end”. With both, the whole string must match. This is how you validate input:
local function is_valid_id(s)
return s:match("^[%l%d_]+:[%l%d_/%.%-]+$") ~= nil
end
print(is_valid_id("minecraft:stone")) --> true
print(is_valid_id("Minecraft:Stone")) --> false
print(is_valid_id("stone")) --> false
Escaping with %
To match a special character literally, put % before it. The special characters are ^ $ ( ) % . [ ] * + - ?:
print(("3.5"):find("%.")) --> 2 2
print(("50%"):match("%d+%%")) --> 50%
Or turn patterns off completely in find with its fourth argument: s:find(".", 1, true).
Captures
Brackets in a pattern capture the part they match. match then returns the captures instead of the whole match:
local cmd = "/give Alex diamond 5"
local who, item, count = cmd:match("^/give (%S+) (%S+) (%d+)$")
print(who, item, tonumber(count)) --> Alex diamond 5
Captures are always strings, so convert numbers with tonumber. If the pattern doesn’t match, match returns nil, which makes it a check and a parser at once:
local x, y, z = ("10 64 -3"):match("^(-?%d+) (-?%d+) (-?%d+)$")
if x then
print(tonumber(x), tonumber(y), tonumber(z)) --> 10 64 -3
end
An empty capture () returns a position instead of text: ("hello"):match("()ll") is 3.
Looping over matches: gmatch
gmatch returns an iterator. Each round of the loop gives the next match:
for word in ("dig, build, fight"):gmatch("%a+") do
print(word)
end
dig
build
fight
With captures, each round gives all the captures. Here it parses key=value pairs:
local settings = {}
for key, value in ("pvp=true difficulty=hard"):gmatch("(%w+)=(%w+)") do
settings[key] = value
end
print(settings.pvp, settings.difficulty) --> true hard
A common split by a separator: s:gmatch("[^,]+") gives every piece between commas.
Replacing: gsub
gsub(pattern, repl) replaces every match. repl can be:
- a string, where
%1,%2refer to captures and%0to the whole match, - a table, where the match is looked up as a key,
- a function, which is called with the captures and returns the new text.
print(("oak_log"):gsub("_", " ")) --> oak log 1
print(("Alex Kai"):gsub("(%a+)", "<%1>")) --> <Alex> <Kai> 2
local colours = { red = "§c", gold = "§6" }
print(("{red}Hi {gold}there"):gsub("{(%a+)}", colours)) --> §cHi §6there 2
print(("diamond sword"):gsub("(%a)(%a*)", function(first, rest)
return first:upper() .. rest
end)) --> Diamond Sword 2
If the function or table gives nil or false, the match stays as it was. A fourth argument limits the number of replacements: s:gsub("a", "b", 1) replaces only the first.
gsub returns two values. When you only want the string, wrap the call in brackets, (s:gsub(...)), or assign it to one variable.
Two special items
%b()matches a balanced pair: an opening(, everything up to the matching). Any two characters work:%b{},%b[].%f[set]is a frontier: it matches the empty spot where the previous character is not in the set and the next one is.%f[%w]%w+finds whole words.
print(("f(a(b)c) d"):match("%b()")) --> (a(b)c)
print(("cat concat cat"):gsub("%f[%w]cat%f[%W]", "dog")) --> dog concat dog 2
Patterns and UTF-8
Patterns work on bytes. . and %a match one byte, so letters such as é or ß (two bytes each) don’t count as %a. To loop over real characters, use utf8.charpattern:
for ch in ("Café"):gmatch(utf8.charpattern) do
io.write(ch, " ")
end
print()
C a f é
Try it
Parse a chat command like "/tp 100 64 -30" into three numbers, returning nil when it doesn’t fit. Then write snake_to_title("deepslate_diamond_ore") that returns "Deepslate Diamond Ore" using gsub twice.
Show a solution
local function parse_tp(text)
local x, y, z = text:match("^/tp (-?%d+) (-?%d+) (-?%d+)$")
if not x then
return nil
end
return tonumber(x), tonumber(y), tonumber(z)
end
print(parse_tp("/tp 100 64 -30")) --> 100 64 -30
print(parse_tp("/tp here")) --> nil
local function snake_to_title(s)
s = s:gsub("_", " ")
return (s:gsub("(%a)(%a*)", function(a, b) return a:upper() .. b end))
end
print(snake_to_title("deepslate_diamond_ore")) --> Deepslate Diamond Ore
s = s:gsub(...) keeps only the first result. The brackets in the return do the same, so the function returns just the string.
Recap: patterns use classes like %d and %a, repeats like + and -, anchors ^ and $, and captures in brackets, with match, gmatch and gsub doing the work.