string.gsub
string.gsub(s, pattern, repl [, n]) string.gsub (global substitute) walks a string, replacing each match of a Lua pattern with repl. It returns the new string and the count of substitutions made. The replacement can be a literal string with %n back-references, a table keyed by capture, or a function called for every match.
Signature
local result, count = string.gsub(s, pattern, repl [, n])
s, the subject string.pattern, a Lua pattern (the same syntaxstring.findandstring.matchaccept).repl, the replacement: a string, a table, or a function.n, optional cap on substitutions. Without it, every match is replaced.
The three replacement modes — string, table, and function — give string.gsub a versatility that few other Lua standard library functions match. When repl is a string, %n tokens reference captures from the pattern. When repl is a table, the first capture becomes the lookup key, making template interpolation nearly a one-liner. And when repl is a function, you get per-match computation with access to every capture, which opens the door to conditional replacement logic that no static pattern can express.
String replacement with back-references
Inside a string repl, %0 is the whole match and %1, %2, … are the captures:
local out, n = string.gsub("hello world", "(%w+) (%w+)", "%2 %1")
print(out, n) --> world hello 1
Use %% to insert a literal percent sign. Back-references are positional, not named — %1 always refers to the first capture group regardless of how many times it matched within the pattern. This is simpler than regex but means you cannot reference the same group multiple times in the replacement string with different meanings.
Limiting the number of replacements
Pass n to stop after the first few matches. The count is per-call, not global, so you can apply graduated replacements by calling gsub in a loop with incrementally higher n values if needed.
string.gsub("aaaa", "a", "b", 2) --> "bbaa" 2
Table replacement
When repl is a table, the first capture (or the whole match when there is none) is used as the key. The value becomes the replacement. A nil or false value leaves the match untouched. Using a table as the replacement is one of gsub’s most productive shortcuts — it turns what would otherwise be a multi-line loop with conditional checking into a single declarative expression.
local vars = {name = "Ada", role = "engineer"}
local tmpl = "Hello ${name}, you are a ${role}."
print((tmpl:gsub("%${(%w+)}", vars)))
-- Hello Ada, you are a engineer.
Function replacement
When repl is a function, it’s called for each match with the captures (or the whole match) as arguments. The return value is converted to a string and used as the replacement. nil or false keeps the original match. The function form is the most powerful of the three because it can make decisions based on the matched text — convert only some matches, skip others, or pull in external data like environment variables or database lookups mid-substitution.
local function upper_word(w) return w:upper() end
print((string.gsub("hello world", "%w+", upper_word)))
-- HELLO WORLD
This is the cleanest way to do per-match transformations. For a more practical example, the pattern below substitutes ${VARIABLE} tokens with environment variable values. The function receives the captured name — HOME from ${HOME} — and looks it up via os.getenv. If the variable is not set, returning "" removes the token silently from the string:
local function envvar(name)
return os.getenv(name) or ""
end
local cmd = "deploy ${HOME}/app"
print((cmd:gsub("%${(%w+)}", envvar)))
-- deploy /home/ada/app
The function callback receives every capture from the pattern as separate arguments — in this example a single capture, but patterns with multiple capture groups pass multiple arguments to the function in the same order the groups appear. When the function returns nil or false, the original match text is left in place rather than being replaced with the string "nil", which is a deliberate and useful design choice for conditional substitution.
Method syntax
("text"):gsub(...) is the same as string.gsub("text", ...):
local trimmed = (" hello "):gsub("^%s+", ""):gsub("%s+$", "")
Note that gsub returns two values; if you chain with :gsub(...) again on the result, wrap in parentheses or capture both returns.
Capture replacement gotchas
%0is the entire match , handy when you want to keep the match and wrap it.%1through%9reference captures; if you write%2but only have one capture, Lua raises an error.- The replacement string is not a regex, so
\nis just two characters; use"\n"(Lua escape) for a newline.
Counting without replacing
Because gsub returns the substitution count as its second value, it doubles as a match counter:
local _, vowels = ("supercalifragilistic"):gsub("[aeiou]", "")
print(vowels) --> 8
See also
- /reference/string-methods/ref-string-find/ , locate a single match.
- /tutorials/lua-fundamentals/strings-and-patterns/ , the tutorial that walks through Lua patterns.
- /guides/lua-string-patterns/ , patterns reference with anchors, classes, and captures.