LearnAI ToolsCareerPractice BuildsPlayContact
Lesson 1021 min read

String Manipulation & Pattern Matching

Master Lua's string library and its own lightweight pattern-matching syntax — find, match, gmatch, gsub, and pattern classes and captures.

Introduction

Lua's string library covers everything from basic formatting to a surprisingly capable, if unconventional, pattern-matching system. In keeping with Lua's small-by-design philosophy, it does not implement full regular expressions — it has its own lighter, faster pattern syntax that covers the vast majority of real-world text processing needs.

What You Will Learn
  • The core string library functions you'll use constantly.
  • How Lua patterns differ from full regular expressions.
  • How to find, match, capture, iterate, and substitute text with patterns.

The Core String Library

local s = "Hello, Lua!"
print(string.upper(s)) -- HELLO, LUA!
print(string.lower(s)) -- hello, lua!
print(string.len(s)) -- 12 (same as #s)
print(string.sub(s, 1, 5)) -- Hello (1-based, inclusive)
print(string.format("%s has %d characters", s, #s))
Output

Click Run to see what this code prints.

Method-Style Syntax Works Too

Since strings have a metatable pointing __index at the string library, s:upper() and string.upper(s) do exactly the same thing — the colon sugar from lesson 7 applies to Lua's built-in string type as well.

Lua Patterns Are Not Regex

Lua patterns look similar to regular expressions at a glance but are a distinct, simpler system — no alternation with |, no non-greedy quantifiers, and different escaping. What they lack in expressive power, they make up for in speed and in keeping Lua's core small.

Pattern ClassMatches
%aAny letter
%dAny digit
%sAny whitespace character
%wAny alphanumeric character
%pAny punctuation character
. (dot)Any character at all
^ and $Anchors: start and end of the string

string.find and string.match

Use case: string.find() returns the start/end indices of the first match; string.match() returns the matched text itself (or captures, covered next) directly — match is what you reach for most often.

local text = "Order #4521 shipped"
local startIdx, endIdx = string.find(text, "%d+")
print(startIdx, endIdx) -- 8 11
local orderNumber = string.match(text, "%d+")
print(orderNumber) -- 4521
Output

Click Run to see what this code prints.

Captures

Wrapping part of a pattern in parentheses creates a capture, letting you pull out multiple specific pieces of a match at once.

local date = "2026-08-12"
local year, month, day = string.match(date, "(%d+)-(%d+)-(%d+)")
print(year, month, day)
Output

Click Run to see what this code prints.

string.gmatch for Iteration

Use case: string.gmatch() returns an iterator function that produces every match in a string one at a time — the natural fit for a generic for loop, exactly like ipairs()/pairs() from lesson 5.

local sentence = "the quick brown fox"
for word in string.gmatch(sentence, "%a+") do
print(word)
end
Output

Click Run to see what this code prints.

string.gsub for Substitution

Use case: string.gsub() replaces every match of a pattern with a replacement string, and also returns the number of replacements made as a second value.

local text = "I like cats and cats like me"
local result, count = string.gsub(text, "cats", "dogs")
print(result)
print("Replacements made:", count)
Output

Click Run to see what this code prints.

Common Mistakes

Avoid These Mistakes
  • Writing a full regular expression (with | alternation or non-greedy ?) and expecting it to work — Lua patterns are a different, smaller system.
  • Forgetting that pattern special characters (. % ( ) . + - * ? [ ] ^ $) need %-escaping when you want to match them literally.
  • Ignoring the second value string.gsub() returns (the replacement count) when you actually need to know how many replacements happened.

Best Practices

  • Reach for string.match() when you want the matched text or captures directly, and string.find() only when you specifically need index positions.
  • Use string.gmatch() for iterating every match instead of manually looping with repeated string.find() calls.
  • Test tricky patterns interactively in the Lua REPL (lesson 3) before dropping them into a larger script.

Frequently Asked Questions

Not with the built-in string library, but third-party LuaRocks libraries (like lrexlib) add full regex support if a project genuinely needs it — most text processing needs are covered by Lua patterns alone.

It keeps the standard library dramatically smaller and the matching engine faster, in line with Lua's core philosophy from lesson 1 — a deliberate tradeoff, not an oversight.

Escape it as %% — the same way you'd escape any of Lua's other pattern special characters when you want them treated literally.

Key Takeaways

  • Lua's string library covers formatting, case conversion, substrings, and length out of the box.
  • Lua patterns are a smaller, faster, distinct system from full regular expressions.
  • string.match() extracts matched text and captures; string.find() returns index positions.
  • string.gmatch() iterates every match; string.gsub() replaces matches and reports how many it replaced.

Summary

Lua's pattern system covers the vast majority of real-world text processing with a much smaller engine than full regex. Next, you'll cover modules and error handling — how to organize Lua code across files and handle failures gracefully.

Next Lesson →

Modules & Error Handling