string Library
The Lua string library provides functions for all common string operations, including length retrieval, substring extraction, case conversion, searching, substitution, formatting, and repetition. It is a standard library that requires no require call, and every function can be called either as a function (string.find(s, pat)) or as a method (s:find(pat)).
All Functions
| Function | Description |
|---|---|
string.len(s) | Returns the number of bytes in string s. |
string.sub(s, i [, j]) | Returns the substring of s from index i to j. When j is omitted, the substring extends to the end. |
string.upper(s) | Returns a copy of s with all ASCII alphabetic characters converted to uppercase. |
string.lower(s) | Returns a copy of s with all ASCII alphabetic characters converted to lowercase. |
string.rep(s, n [, sep]) | Returns a string consisting of n copies of s. When sep is given, it is inserted between copies (Lua 5.2+). |
string.reverse(s) | Returns a string that is the byte-level reverse of s. |
string.byte(s [, i [, j]]) | Returns the byte values of characters at positions i through j as multiple integers (default i=1, j=i). |
string.char(...) | Receives integer byte values and returns a string formed by concatenating the corresponding characters. |
string.format(fmt, ...) | Returns a formatted string according to the format string fmt (equivalent to C's printf). |
string.find(s, pat [, init [, plain]]) | Searches string s for pattern pat and returns the start and end positions of the match plus any captures, or nil if not found. |
string.match(s, pat [, init]) | Returns the first match (or captures) of pattern pat in string s, or nil if not found. |
string.gmatch(s, pat) | Returns an iterator function that yields each match of pattern pat in string s. |
string.gsub(s, pat, repl [, n]) | Returns a copy of s in which all matches of pattern pat are replaced by repl (a string, table, or function), plus the total number of substitutions made. |
string.dump(func [, strip]) | Returns a string containing the bytecode of Lua function func (for serialization). |
Pattern Syntax
string.find(), string.match(), string.gmatch(), and string.gsub() all use Lua's own "pattern" syntax. It is similar to regular expressions but does not support | (alternation) or lookaheads.
| Pattern Element | Meaning |
|---|---|
. | Matches any single character. |
%a | Matches any letter (A-Z, a-z). |
%d | Matches any digit (0-9). |
%l | Matches any lowercase letter. |
%u | Matches any uppercase letter. |
%s | Matches any whitespace character (space, tab, newline, etc.). |
%w | Matches any alphanumeric character (%a + %d). |
%p | Matches any punctuation character. |
%c | Matches any control character. |
%x | Matches any hexadecimal digit (0-9, A-F, a-f). |
%A, %D, ... (uppercase) | Matches the complement of the corresponding class (any character not in that class). |
[set] | Matches any character in the set (e.g. [aeiou], [0-9A-F]). |
[^set] | Matches any character not in the set. |
* | Matches 0 or more repetitions of the preceding element (longest match). |
+ | Matches 1 or more repetitions of the preceding element (longest match). |
? | Matches 0 or 1 occurrence of the preceding element. |
- | Matches 0 or more repetitions of the preceding element (shortest match). |
^ | Anchors the pattern to the start of the string (when placed at the start of the pattern). |
$ | Anchors the pattern to the end of the string (when placed at the end of the pattern). |
(pat) | Capture group. Returns the matched portion as a separate return value. |
%bxy | Matches a balanced string beginning with x and ending with y (e.g. %b() matches balanced parentheses). |
%1–%9 | Back reference. Matches the same text as the n-th capture. |
% (escape) | Escapes a special character (e.g. %., %%). |
Sample Code
psychopass_string.lua
-- psychopass_string.lua — sample for all string library functions
-- Uses PSYCHO-PASS characters to demonstrate each function
-- -----------------------------------------------
-- string.len — byte count
-- -----------------------------------------------
print("=== string.len ===")
local name_akane = "Tsunemori Akane"
local name_shinya = "Kogami Shinya"
local name_nobuchika = "Ginoza Nobuchika"
local name_shogo = "Makishima Shogo"
local name_tomomi = "Masaoka Tomomi"
print(string.format("%-22s len=%d", name_akane, string.len(name_akane)))
print(string.format("%-22s len=%d", name_shinya, string.len(name_shinya)))
print(string.format("%-22s len=%d", name_nobuchika, string.len(name_nobuchika)))
print(string.format("%-22s len=%d", name_shogo, string.len(name_shogo)))
print(string.format("%-22s len=%d", name_tomomi, string.len(name_tomomi)))
-- Method notation yields the same result
print(string.format("Method notation: %s:len() = %d", name_akane, name_akane:len()))
print("")
-- -----------------------------------------------
-- string.sub — extract a substring
-- -----------------------------------------------
print("=== string.sub ===")
-- Split family name and given name from "Tsunemori Akane"
-- family name = bytes 1–9, given name = byte 11 to end
local family_akane = string.sub(name_akane, 1, 9)
local given_akane = string.sub(name_akane, 11)
print(string.format("Family: %s Given: %s", family_akane, given_akane))
-- Negative indices count from the end (-1 is the last byte)
local last5 = string.sub(name_shinya, -5)
local last3 = string.sub(name_nobuchika, -3)
print(string.format("%s last 5 bytes: %s", name_shinya, last5))
print(string.format("%s last 3 bytes: %s", name_nobuchika, last3))
print("")
-- -----------------------------------------------
-- string.upper / string.lower — case conversion
-- -----------------------------------------------
print("=== string.upper / string.lower ===")
local scan_label = "Psycho-Pass Level"
print(string.format("Original : %s", scan_label))
print(string.format("upper : %s", string.upper(scan_label)))
print(string.format("lower : %s", string.lower(scan_label)))
local labels = { "safe", "LATENT_CRIMINAL", "Clear", "ENFORCER" }
print("Normalized (upper):")
for _, label in ipairs(labels) do
print(string.format(" %-20s -> %s", label, label:upper()))
end
print("")
-- -----------------------------------------------
-- string.rep — repeat a string
-- -----------------------------------------------
print("=== string.rep ===")
local gauge_fill = string.rep("*", 8)
local gauge_sep = string.rep("O", 5, "-")
print(string.format("Gauge display : [%s]", gauge_fill))
print(string.format("With separator: %s", gauge_sep))
local function danger_bar(level)
return "[" .. string.rep("#", level) .. string.rep(".", 5 - level) .. "]"
end
local officers = {
{ name = "Tsunemori Akane", level = 1 },
{ name = "Ginoza Nobuchika", level = 2 },
{ name = "Kogami Shinya", level = 4 },
{ name = "Makishima Shogo", level = 5 },
{ name = "Masaoka Tomomi", level = 3 },
}
print("Crime coefficient danger bar:")
for _, officer in ipairs(officers) do
print(string.format(" %-22s %s Lv%d",
officer.name, danger_bar(officer.level), officer.level))
end
print("")
-- -----------------------------------------------
-- string.reverse — reverse a string
-- -----------------------------------------------
print("=== string.reverse ===")
local serials = {
"DOM-001-AKANE",
"DOM-002-SHINYA",
"DOM-003-NOBUCHIKA",
}
for _, serial in ipairs(serials) do
print(string.format(" %-24s -> %s", serial, string.reverse(serial)))
end
print("")
-- -----------------------------------------------
-- string.byte / string.char — byte value conversion
-- -----------------------------------------------
print("=== string.byte / string.char ===")
local code_a = string.byte("Akane")
local code_s = string.byte("Shinya", 1)
local b2, b3, b4 = string.byte("Kogami", 2, 4)
print(string.format("byte('Akane') = %d (char '%s')",
code_a, string.char(code_a)))
print(string.format("byte('Shinya', 1) = %d (char '%s')",
code_s, string.char(code_s)))
print(string.format("byte('Kogami', 2,4) = %d, %d, %d (string '%s')",
b2, b3, b4, string.char(b2, b3, b4)))
-- ASCII 72=H, 83=S, 66=B (initials of Hyper Sonic Bullet)
local acronym = string.char(72, 83, 66)
print(string.format("string.char(72,83,66) = '%s'", acronym))
print("")
-- -----------------------------------------------
-- string.format — formatted output
-- -----------------------------------------------
print("=== string.format ===")
local profiles = {
{ name = "Tsunemori Akane", rank = "Inspector", hue_value = 18, crime_coeff = 9 },
{ name = "Ginoza Nobuchika", rank = "Inspector", hue_value = 62, crime_coeff = 44 },
{ name = "Kogami Shinya", rank = "Enforcer", hue_value = 99, crime_coeff = 146 },
{ name = "Makishima Shogo", rank = "Criminal", hue_value = 999, crime_coeff = 299 },
{ name = "Masaoka Tomomi", rank = "Enforcer", hue_value = 88, crime_coeff = 130 },
}
print(string.rep("-", 58))
for _, p in ipairs(profiles) do
print(string.format("%-22s %-12s Hue:%3d CrimeCoeff:%3d",
p.name, p.rank, p.hue_value, p.crime_coeff))
end
print("")
-- -----------------------------------------------
-- string.find — pattern search (returns positions)
-- -----------------------------------------------
print("=== string.find ===")
local report = "Dominator activated. Kogami Shinya engaged target. Psycho-Pass clear."
-- Plain literal search (plain=true)
local s1, e1 = string.find(report, "Kogami", 1, true)
print(string.format("Position of 'Kogami': %d-%d", s1, e1))
-- Find first capitalized word using a pattern
local s2, e2, cap2 = string.find(report, "(%u%a+)", 1)
print(string.format("First capitalized word: '%s' (pos %d-%d)", cap2, s2, e2))
-- Returns nil when not found
local result = string.find(report, "Tsunemori")
print(string.format("Search for 'Tsunemori': %s", tostring(result)))
print("")
-- -----------------------------------------------
-- string.match — pattern match (returns content)
-- -----------------------------------------------
print("=== string.match ===")
local log1 = "CrimeCoeff:146 Enforcer:Kogami"
local log2 = "CrimeCoeff:299 Target:Makishima"
local log3 = "CrimeCoeff:9 Inspector:Tsunemori"
local function extract_coeff(log)
return string.match(log, "CrimeCoeff:(%d+)")
end
print(string.format("log1 crime coeff: %s", extract_coeff(log1)))
print(string.format("log2 crime coeff: %s", extract_coeff(log2)))
print(string.format("log3 crime coeff: %s", extract_coeff(log3)))
local enforcer, coeff = string.match(log1, "CrimeCoeff:(%d+) Enforcer:(%a+)")
print(string.format("Enforcer=%s CrimeCoeff=%s", enforcer, coeff))
print("")
-- -----------------------------------------------
-- string.gmatch — iterate over all matches
-- -----------------------------------------------
print("=== string.gmatch ===")
local patrol_route = "Sector-A,Sector-C,Sector-F,Sector-G,Sector-H"
print("Patrol sectors:")
for sector in string.gmatch(patrol_route, "[^,]+") do
print(string.format(" %s", sector))
end
local name_list = "Tsunemori Akane, Ginoza Nobuchika, Kogami Shinya"
print("Name split (family given):")
for family, given in string.gmatch(name_list, "(%a+) (%a+)") do
print(string.format(" Family=%-12s Given=%s", family, given))
end
print("")
-- -----------------------------------------------
-- string.gsub — pattern substitution
-- -----------------------------------------------
print("=== string.gsub ===")
local classified = "Makishima Shogo was seen near the Sibyl System terminal."
local censored, count = string.gsub(classified, "Sibyl System", "[REDACTED]")
print(string.format("After substitution: %s", censored))
print(string.format("Substitution count: %d", count))
-- Pass a function to generate replacement strings dynamically
local raw_log = "Kogami:146, Masaoka:130, Makishima:299, Akane:9, Ginoza:44"
local masked = string.gsub(raw_log, "%d+", function(n)
return string.rep("*", #n)
end)
print(string.format("Masked numbers: %s", masked))
-- Use a table to map names
local name_map = {
Tsunemori = "常守",
Kogami = "狡噛",
Ginoza = "宜野座",
Makishima = "槙島",
Masaoka = "征陸",
}
local jp_log = string.gsub(raw_log, "%a+", name_map)
print(string.format("Japanese names: %s", jp_log))
print("")
-- -----------------------------------------------
-- Pattern advanced: %b() matches balanced brackets
-- -----------------------------------------------
print("=== Pattern advanced: %b() ===")
local sys_log = "execute(scan_target) result(clear) action(stand_by)"
print("Arguments inside parentheses:")
for inner in string.gmatch(sys_log, "%b()") do
local content = string.sub(inner, 2, -2)
print(string.format(" %s", content))
end
print("")
-- -----------------------------------------------
-- Summary: extract and format information from a string
-- -----------------------------------------------
print("=== Summary: parsing a Dominator usage log ===")
local dominator_log = [[
2114-12-03 08:42 Kogami:146 target:Makishima mode:Lethal_Eliminator
2114-12-03 09:15 Akane:9 target:Victim01 mode:Non_Lethal_Paralyzer
2114-12-03 11:30 Ginoza:44 target:Suspect01 mode:Non_Lethal_Paralyzer
2114-12-03 14:20 Masaoka:130 target:Suspect02 mode:Lethal_Eliminator
]]
print(string.format("%-12s %-8s %-10s %-10s %s",
"Datetime", "Officer", "CrimeCoeff", "Target", "Mode"))
print(string.rep("-", 70))
for line in string.gmatch(dominator_log, "[^\n]+") do
local date, time_str, officer, coeff_str, target, mode =
string.match(line,
"(%d+%-%d+%-%d+) (%d+:%d+) (%a+):(%d+) target:(%S+) mode:(%S+)")
if officer then
print(string.format("%-12s %-8s %-10s %-10s %s",
date .. " " .. time_str, officer, coeff_str, target, mode))
end
end
lua psychopass_string.lua
=== string.len ===
Tsunemori Akane len=15
Kogami Shinya len=13
Ginoza Nobuchika len=16
Makishima Shogo len=15
Masaoka Tomomi len=14
Method notation: Tsunemori Akane:len() = 15
=== string.sub ===
Family: Tsunemori Given: Akane
Kogami Shinya last 5 bytes: hinya
Ginoza Nobuchika last 3 bytes: ika
=== string.upper / string.lower ===
Original : Psycho-Pass Level
upper : PSYCHO-PASS LEVEL
lower : psycho-pass level
Normalized (upper):
safe -> SAFE
LATENT_CRIMINAL -> LATENT_CRIMINAL
Clear -> CLEAR
ENFORCER -> ENFORCER
=== string.rep ===
Gauge display : [********]
With separator: O-O-O-O-O
Crime coefficient danger bar:
Tsunemori Akane [#....] Lv1
Ginoza Nobuchika [##...] Lv2
Kogami Shinya [####.] Lv4
Makishima Shogo [#####] Lv5
Masaoka Tomomi [###..] Lv3
=== string.reverse ===
DOM-001-AKANE -> ENAKA-100-MOD
DOM-002-SHINYA -> AYNIHS-200-MOD
DOM-003-NOBUCHIKA -> AKIHCUBON-300-MOD
=== string.byte / string.char ===
byte('Akane') = 65 (char 'A')
byte('Shinya', 1) = 83 (char 'S')
byte('Kogami', 2,4) = 111, 103, 97 (string 'oga')
string.char(72,83,66) = 'HSB'
=== string.format ===
----------------------------------------------------------
Tsunemori Akane Inspector Hue: 18 CrimeCoeff: 9
Ginoza Nobuchika Inspector Hue: 62 CrimeCoeff: 44
Kogami Shinya Enforcer Hue: 99 CrimeCoeff:146
Makishima Shogo Criminal Hue:999 CrimeCoeff:299
Masaoka Tomomi Enforcer Hue: 88 CrimeCoeff:130
=== string.find ===
Position of 'Kogami': 22-27
First capitalized word: 'Dominator' (pos 1-9)
Search for 'Tsunemori': nil
=== string.match ===
log1 crime coeff: 146
log2 crime coeff: 299
log3 crime coeff: 9
Enforcer=146 CrimeCoeff=Kogami
=== string.gmatch ===
Patrol sectors:
Sector-A
Sector-C
Sector-F
Sector-G
Sector-H
Name split (family given):
Family=Tsunemori Given=Akane
Family=Ginoza Given=Nobuchika
Family=Kogami Given=Shinya
=== string.gsub ===
After substitution: Makishima Shogo was seen near the [REDACTED] terminal.
Substitution count: 1
Masked numbers: Kogami:***, Masaoka:***, Makishima:***, Akane:*, Ginoza:**
Japanese names: 狡噛:146, 征陸:130, 槙島:299, Akane:9, 宜野座:44
=== Pattern advanced: %b() ===
Arguments inside parentheses:
scan_target
clear
stand_by
=== Summary: parsing a Dominator usage log ===
Datetime Officer CrimeCoeff Target Mode
----------------------------------------------------------------------
2114-12-03 08:42 Kogami 146 Makishima Lethal_Eliminator
2114-12-03 09:15 Akane 9 Victim01 Non_Lethal_Paralyzer
2114-12-03 11:30 Ginoza 44 Suspect01 Non_Lethal_Paralyzer
2114-12-03 14:20 Masaoka 130 Suspect02 Lethal_Eliminator
Common Mistakes
Confusing function notation and method notation without understanding the difference
string.find(s, pat) (function notation) and s:find(pat) (method notation) are both valid. Method notation passes s as the implicit first argument (self) through the string metatable, so the two forms are internally equivalent. Mixing them carelessly makes it easy to pass s twice by accident.
-- NG: intending method notation but passing s a second time as an explicit argument local s = "Kogami Shinya" local result = s:find(s, "Kogami") -- s is passed twice (once as self, once as the pattern) print(result) -- nil (searching for the pattern "Kogami Shinya" starting at "Kogami")
-- OK: pick one style and be explicit about the arguments
local s = "Kogami Shinya"
local result = string.find(s, "Kogami") -- function notation: arguments are s, pat
print(result) -- 1
-- or
local result2 = s:find("Kogami") -- method notation: self is s, argument is pat only
print(result2) -- 1
Using string.byte / string.char on multi-byte characters
string.byte() returns individual byte values. A Japanese character in UTF-8 occupies 3 bytes, so extracting only the first byte does not give a meaningful character value. string.char() likewise assembles a string from single byte values and cannot reconstruct multi-byte characters.
-- NG: using string.byte on a Japanese string produces unexpected values local name = "常守朱" local b = string.byte(name, 1) print(b) -- 229 (first byte of UTF-8 sequence for 常, not the codepoint) print(string.char(b)) -- 'å' (a single byte, not the original character)
-- OK: use string.byte / string.char with ASCII strings local code = "Tsunemori Akane" local b = string.byte(code, 1) print(b) -- 84 (ASCII code for 'T') print(string.char(b)) -- T
Writing a manual loop instead of using the third argument of string.rep
string.rep(s, n, sep) accepts a third argument sep that is inserted between each copy of s (Lua 5.2+). Not knowing about this argument leads to writing unnecessary loops.
-- NG: implementing a separator-joined repeat with a manual loop
local items = {}
for i = 1, 5 do
items[i] = "Dominator"
end
local result = table.concat(items, " | ")
print(result) -- works but verbose
-- OK: use the third argument of string.rep for the separator
local result = string.rep("Dominator", 5, " | ")
print(result) -- Dominator | Dominator | Dominator | Dominator | Dominator
Overview
The string library is a standard library that is available without any require call. Every function can also be called as a method (s:upper() etc.). string.len() and string.sub() cover basic length and slicing; string.format() provides C printf-style formatting. The four pattern-matching functions (find, match, gmatch, gsub) use Lua's own pattern syntax — similar to regular expressions but without alternation or lookaheads. Pattern class specifiers such as %d, %a, and %s are combined with quantifiers *, +, ?, and -. Note that - is the shortest match quantifier while * is the longest. The third argument of string.gsub() can be a string, table, or function, enabling everything from simple replacement to dynamic transformation. For individual function details, see also string.find() / string.match(), string.gsub(), and string.format().
If you find any errors or copyright issues, please contact us.