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:

PatternMatchesExample
.any byte"hi!" → "h"
%aa letter"42 apples" → "a"
%da digit"Level 42" → "4"
%la lowercase letter"ABCdef" → "d"
%uan uppercase letter"abcDEF" → "D"
%wa letter or digit"--x9" → "x"
%xa hexadecimal digit"0xFF" → "0"
%swhitespace: space, tab, newline, carriage return, vertical tab and form feed"a b" → " "
%ppunctuation"hello, world" → ","
%ca control character"tab\there" → "\t"
%ga 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:

PatternMatchesExample
%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.

PatternMatchesExample
%.a dot"v1.2" → "."
%%a percent sign"100%" → "%"
%(.-%)parentheses and what's between them"f(x)" → "(x)"
a%+bthe text "a+b""1 + a+b" → "a+b"

string.find compares a pattern as plain text when its fourth argument is true:

Input
print(string.find("a.b", "."))
print(string.find("a.b", ".", 1, true))
Output
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.

PatternMatchesExample
[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.

PatternMatchesExample
%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:

PatternMatchesExample
(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.

PatternMatchesExample
^%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 ^:

Input
for found in string.gmatch("^a ^b c", "^%a") do
    print(found)
end
Output
^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.

PatternMatchesExample
(%a+)@the letters before an @"ada@example.com" → "ada"
()%dthe position of the first digit"abc1" → 4
(%a)%1a letter written twice in a row"hello" → "l"
Input
const key, value = string.match("volume = 11", "^(%a+)%s*=%s*(%d+)$")
print(key)
print(value)
Output
volume
11

In string.gsub's replacement string, %1 to %9 stand for the captures and %0 for the whole match:

Input
print((string.gsub("Ada Lovelace", "(%a+) (%a+)", "%2, %1")))
print((string.gsub("hp=30", "%d+", "[%0]")))
Output
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.

PatternMatchesExample
%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"
Input
print((string.gsub("the cat concatenates", "%f[%a]cat%f[%A]", "dog")))
Output
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.

Input
const setting = "  volume = 11  "
const key, value = table.unpack(setting:trim():split(" = ", 2))
print(key, value)
print(#("first\nsecond"):split("\n"))
Output
volume	11
2
ToUse
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_]*$
Input
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_]*$"))
Output
too many spaces
png
hi
-12
nil
snake_case2