string.match
string.match(s, pattern [, init]) Overview
string.match scans a string for the first match of a Lua pattern and returns the captured substrings. If the pattern has no captures, it returns the whole match. If nothing matches, it returns fail (in Lua 5.4 that value is nil today, but the manual reserves the right to change it).
string.match(s, pattern [, init])
s is the subject string. pattern is a Lua pattern (see the Pattern cheat sheet below, or the full Lua String Patterns guide). init is an optional 1-based byte position where the search starts; it defaults to 1 and can be negative.
A minimal call:
print(string.match("hello world", "world"))
-- output: world
string.match is the right tool when you want the matched text itself, not its position. If you need the start and end byte indices instead, see string.find.
Parameters
| # | Name | Type | Required | Default | Description |
|---|---|---|---|---|---|
| 1 | s | string | yes | — | The string to search. Must actually be a string; numbers are not implicitly coerced. |
| 2 | pattern | string | yes | — | A Lua pattern. Magic characters ^ $ ( ) % . [ ] * + - ? must be escaped with %. |
| 3 | init | integer | no | 1 | Byte index where the search starts. Negative values count from the end (-1 is the last byte). Values past either end of the string just produce a no-match result. |
Return values
string.match returns a variable number of strings:
- If the pattern matches and has at least one capture, it returns one string per capture, in left-parenthesis order.
- If the pattern matches and has no captures, it returns the whole match (a single string).
- If the pattern does not match, it returns
fail. - If the pattern is malformed (e.g. an unbalanced
[), it raises an error.
The “no captures → whole match” rule is the most common source of confusion with this function. To get both the whole match and the captures, wrap the whole pattern in an explicit () capture, or pair it with string.find which returns the start and end positions as the first two values. Side-by-side:
-- no captures: returns the whole match
print(string.match("user_42_session", "%w+_%d+_%w+"))
-- output: user_42_session
-- with captures: returns each () group in order
local head, mid, tail = string.match("user_42_session", "(%w+)_(%d+)_(%w+)")
print(head, mid, tail)
-- output: user 42 session
A note on fail: in Lua 5.4 the sentinel currently equals nil, but the manual warns that this may change in a future version. Test for “no match” with if not string.match(...) rather than ... == nil.
Examples
Example 1: Whole match when there are no captures
A pattern with no parentheses returns the entire matched substring. This is the simplest way to confirm that a pattern matches at all, since any non-nil return proves the search found something in the input.
print(string.match("hello world", "world"))
-- output: world
Example 2: Capturing a substring
Add a (...) group around the part you want to extract. With one capture, the return is a single string. Captures are substrings of the input; if you need them as numbers, pipe the result through tonumber afterwards.
local year = string.match("built 2024-06-10", "(%d%d%d%d)")
print(year)
-- output: 2024
Example 3: Multiple captures
Each (...) becomes its own return value. Use multiple assignment to grab them all in one go. The order of returns matches the order of the left parentheses in the pattern, not the order of the bytes in the input string.
local y, m, d = string.match("2024-06-10", "(%d%d%d%d)%-(%d%d)%-(%d%d)")
print(y, m, d)
-- output: 2024 06 10
Example 4: Using the init argument
Pass init to start the search further into the string. This is how you skip past the first occurrence, or restrict a search to the right half of a long line of text.
print(string.match("foo bar foo baz", "foo", 5))
-- output: nil (no 'foo' from position 5 onward)
print(string.match("foo bar foo baz", "foo", 9))
-- output: foo (matches the second 'foo')
Example 5: Negative init
Negative values count from the end of the string. -1 is the last byte, -2 is the second-to-last, and so on. This is the same convention used by string.sub and string.byte, and it makes it easy to anchor a search to the tail of an input.
-- "name=value" has 10 bytes; -3 points at position 8, which is '='
print(string.match("name=value", "=", -3))
-- output: =
Example 6: Match boundaries with empty () groups
Empty captures () record the current position as a number. A pair of empty captures at the start and end of a pattern gives you the match boundaries as numbers, alongside the named captures. The numbers are 1-based, and the right edge is the position one past the last matched byte.
local left, head, num, tail, right =
string.match("user_42_session", "()(%w+)_(%d+)_(%w+)()")
-- left = 1 (start of match, as a number)
-- head = "user"
-- num = "42"
-- tail = "session"
-- right = 14 (one past the end of match, as a number)
Pattern cheat sheet
Lua patterns are not regular expressions. The building blocks you will reach for most often:
Character classes. . (any byte), %a (letter), %d (digit), %s (whitespace), %w (alphanumeric), %u/%l (uppercase/lowercase letter). Uppercase forms are complements, so %S is non-whitespace. Letter classes follow the C locale; prefer [a-z] for ASCII-only matching.
Sets. [set] matches any one byte in the set; [^set] is the complement. Inside a set, escape magic characters with %, and place - at the start or end of the set to treat it as a literal hyphen.
Quantifiers. * (0 or more, greedy), + (1 or more, greedy), - (0 or more, lazy, picks the shortest match), ? (0 or 1, prefers 1).
Anchors. ^ matches only at position 1, $ only at #s. They are literal characters at other positions, so unanchored patterns can match anywhere in the string.
Captures and backreferences. (...) is a numbered capture. %1 through %9 refer back to earlier captures in the same pattern.
For the full grammar with worked examples, see the Lua String Patterns guide.
string.match vs string.find vs string.gmatch
All three work on Lua patterns but they return different things.
string.matchreturns the captured substrings (or the whole match). Reach for it when you want the value, not its position.string.findreturns the start and end byte indices, followed by the captures. Use it when you need to slice the string, or when the position itself is what you care about.string.gmatchreturns an iterator that yields each match in turn. Use it when you want every occurrence, not just the first.
If you need to substitute matches with a replacement string, that is string.gsub.
Common gotchas
-
Testing for no match with
== nil. Thefailvalue isnilin Lua 5.4 but the manual reserves the right to change it. Useif not string.match(s, pat) then ...to stay forward-compatible. The same advice applies tostring.findandstring.gmatch. -
Expecting regex features. Lua patterns have no
|alternation, no\d, no\b, no{n,m}. Use%d,%f[%w], and explicit character-class repetitions instead. -
Locale-dependent classes.
%a,%l,%ufollow the C locale. If you need ASCII-only letter matching, use[a-zA-Z]rather than%a. -
Greedy vs lazy quantifiers.
*and+match as much as possible;-matches as little as possible. On the input"a(b(c))", the pattern"a%(.-)%)"lazily matches"(b(c))", picking the shortest valid match. Reach for-whenever the input can contain nested delimiters. -
Anchors are not implicit. A pattern without
^or$can match anywhere in the string. If you want a full-string match, anchor both ends:"^...$". -
Captures vs whole match. Without
(...), you get the whole match. To get both, wrap the whole pattern in an outer capture:"(whole (capture))". -
Empty match at a position. A pattern like
"()"matches at every position and returns position numbers via empty captures. Note that the “skip empty matches after a previous match” rule from the manual applies tostring.gsubandstring.gmatch, not tostring.match(which returns only the first occurrence anyway). -
initpast the end is not an error.string.match(s, pat, 9999)returnsfail, just like a regular no-match. -
Bad patterns raise errors. An unbalanced
[throws a runtime error, not afail. Wrap user-supplied patterns inpcallif you accept them from outside.
Working with untrusted patterns
If you accept patterns from outside the program (config files, user input), wrap the call in pcall so a malformed pattern raises a recoverable error instead of crashing the script:
local ok, result = pcall(string.match, s, user_pattern)
if not ok then
-- pattern was malformed; surface a friendly error
elseif result == nil then
-- pattern was valid but found nothing in `s`
else
-- `result` holds the captured substrings (or the whole match)
end
This works because pattern parsing happens at the call site, not at search time, so a bad pattern would otherwise throw a fatal error on the first attempt.
See also
string.find— find the position of a match and its captures.string.gmatch— iterate over all matches in a string.string.gsub— find and replace matches in a string.- Lua String Patterns — full pattern grammar with worked examples.
- Strings and Patterns — beginner intro to the string library.