Patterns
A pattern is a string that describes the text to look for, like "%d+" for a run of digits. string.find, string.match, string.gmatch and string.gsub take one.
Patterns are smaller than regular expressions. They have no alternation like a|b, no counted repetition like {2,4}, and a repetition applies to one character class, never to a group. They match bytes, and their classes only know ASCII. To look for plain text, use string.contains, string.replace or string.split, which never read patterns.
Character classes
A character class matches one byte from a group of bytes:
| Pattern | Matches | Example |
|---|---|---|
. | any byte | "hi!" → "h" |
%a | a letter | "42 apples" → "a" |
%d | a digit | "Level 42" → "4" |
%l | a lowercase letter | "ABCdef" → "d" |
%u | an uppercase letter | "abcDEF" → "D" |
%w | a letter or digit | "--x9" → "x" |
%x | a hexadecimal digit | "0xFF" → "0" |
%s | whitespace: space, tab, newline, carriage return, vertical tab and form feed | "a b" → " " |
%p | punctuation | "hello, world" → "," |
%c | a control character | "tab\there" → "\t" |
%g | a printable character other than space | " ok" → "o" |
The uppercase form of a class matches every byte the lowercase one doesn't. %S matches anything but whitespace and %D anything but a digit:
| Pattern | Matches | Example |
|---|---|---|
%S+ | a run of bytes that aren't whitespace | " first word" → "first" |
%D+ | a run of bytes that aren't digits | "42 apples" → " apples" |
Special characters
These characters have a meaning of their own in a pattern: ^ $ ( ) % . [ ] * + - ?. A % before any of them matches the character itself. Every other character matches itself.
| Pattern | Matches | Example |
|---|---|---|
%. | a dot | "v1.2" → "." |
%% | a percent sign | "100%" → "%" |
%(.-%) | parentheses and what's between them | "f(x)" → "(x)" |
a%+b | the text "a+b" | "1 + a+b" → "a+b" |
string.find compares a pattern as plain text when its fourth argument is true:
print(string.find("a.b", "."))
print(string.find("a.b", ".", 1, true))1 1 2 2
Sets
Square brackets make a set that matches any one byte in it. A set can hold single characters, ranges like a-z and classes like %d. A ^ right after the opening bracket matches any byte outside the set.
| Pattern | Matches | Example |
|---|---|---|
[aeiou] | a vowel | "rhythm and blues" → "a" |
[a-f]+ | a run of letters from a to f | "zzbeefzz" → "beef" |
[%d.]+ | a run of digits and dots | "version 1.2.3" → "1.2.3" |
[^,]+ | a run of anything but commas | "red,green" → "red" |
[%]%-] | a closing bracket or a dash, escaped with % | "a-b" → "-" |
Repetition
A class or set followed by one of these characters matches a run of it. * and + take the longest run that still lets the rest of the pattern match. - takes the shortest.
| Pattern | Matches | Example |
|---|---|---|
%d* | zero or more digits, as many as it can | "42abc" → "42" |
%d+ | one or more digits, as many as it can | "abc42" → "42" |
<.*> | the longest text between < and > | "<a><b>" → "<a><b>" |
<.-> | the shortest text between < and > | "<a><b>" → "<a>" |
-?%d+ | an optional minus sign, then digits | "x = -7" → "-7" |
A repetition character after a group doesn't repeat the group. It matches itself instead, so (ab)+ looks for ab+ with a plus sign:
| Pattern | Matches | Example |
|---|---|---|
(ab)+ | ab followed by a plus sign | "abab ab+" → "ab" |
Anchors
A ^ at the start of a pattern makes it match only at the start of the string, and a $ at the end only at the end. Anywhere else they match themselves.
| Pattern | Matches | Example |
|---|---|---|
^%d+ | digits at the start | "42 apples" → "42" |
^%d+ | digits at the start | "apples 42" → nil |
%d+$ | digits at the end | "apples 42" → "42" |
^%s*$ | a string that's empty or only whitespace | " " → " " |
string.gmatch doesn't anchor. A ^ at the start of its pattern matches a literal ^:
for found in string.gmatch("^a ^b c", "^%a") do
print(found)
end^a ^b
Captures
Parentheses capture the text their part of the pattern matches. string.match returns each capture in order, and returns the whole match only when the pattern has none. Empty parentheses () capture the position as a number. %1 to %9 match the text of an earlier capture again. A pattern can have up to 32 captures.
| Pattern | Matches | Example |
|---|---|---|
(%a+)@ | the letters before an @ | "ada@example.com" → "ada" |
()%d | the position of the first digit | "abc1" → 4 |
(%a)%1 | a letter written twice in a row | "hello" → "l" |
const key, value = string.match("volume = 11", "^(%a+)%s*=%s*(%d+)$")
print(key)
print(value)volume 11
In string.gsub's replacement string, %1 to %9 stand for the captures and %0 for the whole match:
print((string.gsub("Ada Lovelace", "(%a+) (%a+)", "%2, %1")))
print((string.gsub("hp=30", "%d+", "[%0]")))Lovelace, Ada hp=[30]
Balanced text and frontiers
%bxy matches a run from x to the y that balances it, like %b() for a parenthesized expression with parentheses inside. %f[set] is a frontier: it matches no text, only the place where the byte before isn't in the set and the byte after is. It finds the edges of words.
| Pattern | Matches | Example |
|---|---|---|
%b() | parentheses with the text between, nested ones included | "f(a(b))c" → "(a(b))" |
%b"" | a double-quoted string | "say \"hi\" now" → "\"hi\"" |
%f[%a]%a+ | a whole word | " hello" → "hello" |
%f[%a]cat%f[%A] | the word cat, but not cat inside another word | "concat cat" → "cat" |
print((string.gsub("the cat concatenates", "%f[%a]cat%f[%A]", "dog")))the dog concatenates
Common patterns
Luwu's string functions do the jobs Lua scripts used to write a pattern for. string.trim trims whitespace, string.split splits lines and reads a key and value, and string.contains, startswith and endswith check for plain text. The patterns below do what those functions can't.
const setting = " volume = 11 "
const key, value = table.unpack(setting:trim():split(" = ", 2))
print(key, value)
print(#("first\nsecond"):split("\n"))volume 11 2
| To | Use |
|---|---|
| Find each word | %a+ |
| Find each run of text between spaces | %S+ |
| Collapse runs of whitespace | %s+ |
| Get a file's extension | %.([^./]+)$ |
| Get the text between quotes | "([^"]*)" |
| Check for a whole integer | ^-?%d+$ |
| Check for an identifier | ^[%a_][%w_]*$ |
print((string.gsub("too many spaces", "%s+", " ")))
print(string.match("photo.final.png", "%.([^./]+)$"))
print(string.match('say "hi" twice', '"([^"]*)"'))
print(string.match("-12", "^-?%d+$"))
print(string.match("12.5", "^-?%d+$"))
print(string.match("snake_case2", "^[%a_][%w_]*$"))too many spaces png hi -12 nil snake_case2