Reading — step 1 of 5
Learn
String Patterns and Matching
You have already used one of these without being told what it was. Back in String Basics, splitting a line into words looked like this:
for w in ("to be or not"):gmatch("%S+") do io.write(w, " ") end
--> to be or not
"%S+" is a Lua pattern. It is not a regular expression — Lua ships its own
smaller notation, and the differences bite hardest when you assume otherwise.
Character classes
A class matches one character. Upper-case negates it.
| Class | Matches |
|---|---|
%d | a digit |
%a | a letter |
%s | whitespace |
%S | anything that is not whitespace |
%w | a letter or digit |
%p | punctuation |
+ after a class means "one or more". So %d+ is a run of digits and %S+ is a
run of non-space characters — a word:
print(("order 42 shipped"):match("%d+"))
print(("ab12"):match("%a+"), ("ab12"):match("%w+"))
print(("a1b22c"):gsub("%d", "#"))
--> 42
--> ab ab12
--> a#b##c 3
%a+ stops at the 1 because a digit is not a letter; %w+ keeps going because
it is. And notice gsub returned two values — the new string and how many
substitutions it made.
Captures
Parentheses capture, and match returns one value per capture:
local y, m = ("2026-07-27"):match("(%d+)-(%d+)")
print(y, m)
--> 2026 07
The magic characters
^ $ ( ) % . [ ] * + - ? mean something in a pattern. To match one literally,
escape it with % — not with a backslash:
print(("a.b"):find("."))
print(("a.b"):find(".", 1, true))
print(("a.b"):match("%."))
--> 1 1
--> 2 2
--> .
The first line is the trap. . matches any character, so it found position 1.
The second passes true as find's fourth argument — plain text search, no
pattern. The third escapes the dot properly.
What Lua patterns do NOT have
This is the part that catches people arriving from other languages. There is no alternation:
print(("abc"):match("a|b"))
--> nil
No error, no warning — a|b is read as the literal three characters a, |,
b, which simply are not in "abc". Silence, not a crash. ? does work, for an
optional single character:
print(("color"):match("colou?r"), ("colour"):match("colou?r"))
--> color colour
Anchors
^ at the start means "must match here"; $ at the end means "must reach the
end".
print(("hello"):match("^h"), ("hello"):match("o$"))
--> h o
Common mistakes
- Assuming regex syntax works — no
|, no\d, no{2,3}. Classes are%dstyle and there is no alternation. - Escaping with a backslash — Lua escapes pattern magic with
%, so a literal dot is%., not\.. - Forgetting
findreturns positions — it gives you start and end indices;matchgives you the text. - Ignoring
gsub's second return value — it is the substitution count, and it is often exactly the number you wanted to compute. - Using
.when you meant a dot — it matches everything, so the bug looks like "my pattern is too greedy".
Your exercise
Line Fields reads a count, then that many lines, and prints one summary line
each in the form <first-number>|<word-count>|<punctuation-stripped>.
The first number is the first run of digits — none when the line has none. The
word count is how many %S+ runs the line contains. The stripped text is the line
with every %p character removed.
order 42, shipped! therefore gives 42|3|order 42 shipped. Watch the first
field on a1b22c: the first run of digits is 1, not 122.
Discussion
Ask a question, share an insight, or help someone who’s stuck.
Sign in to post a comment or reply.
Loading…