luaguides

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 syntax string.find and string.match accept).
  • 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

  • %0 is the entire match , handy when you want to keep the match and wrap it.
  • %1 through %9 reference captures; if you write %2 but only have one capture, Lua raises an error.
  • The replacement string is not a regex, so \n is 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