Lua - Strings
Overview
Estimated time: 30–35 minutes
Strings are sequences of characters used to represent text. Lua provides powerful string manipulation capabilities through string literals, concatenation, pattern matching, and a comprehensive string library. This tutorial covers all aspects of working with strings in Lua.
Learning Objectives
- Master string creation and different literal formats
- Learn string concatenation and formatting techniques
- Explore the string library and common functions
- Understand Lua pattern matching (regex alternative)
- Apply string manipulation in practical scenarios
Prerequisites
- Understanding of Lua variables and operators
- Knowledge of basic functions and tables
String Literals
Lua supports several ways to create string literals:
Single and Double Quotes
-- Single quotes
local message1 = 'Hello, World!'
local name = 'Alice'
-- Double quotes
local message2 = "Hello, World!"
local greeting = "Welcome to Lua!"
-- Using quotes inside strings
local quote1 = "He said, 'Hello!'"
local quote2 = 'She replied, "Hi there!"'
-- Escaping quotes
local escaped1 = "He said, \"Hello!\""
local escaped2 = 'She replied, \'Hi there!\''
print(message1)
print(quote1)
print(escaped1)
Expected Output:
Hello, World!
He said, 'Hello!'
He said, "Hello!"
Long Strings (Multi-line)
-- Basic long string
local poem = [[
Roses are red,
Violets are blue,
Lua is awesome,
And so are you!
]]
-- Long string with custom delimiters (to avoid conflicts)
local code_snippet = [=[
local function greet()
print([[Hello, World!]])
end
greet()
]=]
-- Long strings preserve formatting
local formatted_text = [[
This text preserves
its indentation
and spacing
]]
print("Poem:", poem)
print("Code snippet:", code_snippet)
print("Formatted:", formatted_text)
Escape Sequences
Special characters can be included using escape sequences:
-- Common escape sequences
local newline = "Line 1\nLine 2"
local tab_separated = "Name\tAge\tCity"
local backslash = "This is a backslash: \\"
local quote = "He said: \"Hello!\""
local carriage_return = "Windows line ending\r\n"
-- Unicode escapes (Lua 5.3+)
local unicode_char = "\u{1F600}" -- Emoji
local chinese = "\u{4E2D}\u{6587}" -- Chinese characters
-- Numeric escapes
local bell = "\a" -- Alert (bell)
local backspace = "\b"
local form_feed = "\f"
local vertical_tab = "\v"
print("Newline example:")
print(newline)
print("\nTab separated:")
print(tab_separated)
print("\nBackslash:", backslash)
print("Quote:", quote)
Expected Output:
Newline example:
Line 1
Line 2
Tab separated:
Name Age City
Backslash: This is a backslash: \
Quote: He said: "Hello!"
String Concatenation
Combine strings using the ..
operator:
-- Basic concatenation
local first_name = "John"
local last_name = "Doe"
local full_name = first_name .. " " .. last_name
-- Multiple concatenations
local greeting = "Hello, " .. first_name .. "! Welcome to " .. "Lua programming."
-- Number to string conversion (automatic)
local age = 25
local introduction = "My name is " .. full_name .. " and I am " .. age .. " years old."
-- Concatenation with assignment
local message = "Current time: "
message = message .. os.date("%H:%M:%S")
print("Full name:", full_name)
print("Greeting:", greeting)
print("Introduction:", introduction)
print("Message:", message)
Expected Output:
Full name: John Doe
Greeting: Hello, John! Welcome to Lua programming.
Introduction: My name is John Doe and I am 25 years old.
Message: Current time: 12:34:56
String Length
Get string length using the #
operator:
local text = "Hello, Lua!"
local empty_string = ""
local unicode_text = "こんにちは" -- Japanese
print("Length of 'Hello, Lua!':", #text)
print("Length of empty string:", #empty_string)
print("Length of unicode text:", #unicode_text)
-- Length in a function
local function count_characters(str)
return "The string '" .. str .. "' has " .. #str .. " characters."
end
print(count_characters("Programming"))
print(count_characters("A"))
-- Comparing lengths
local password = "mypassword123"
if #password >= 8 then
print("Password is long enough")
else
print("Password too short, minimum 8 characters")
end
Expected Output:
Length of 'Hello, Lua!': 11
Length of empty string: 0
Length of unicode text: 15
The string 'Programming' has 11 characters.
The string 'A' has 1 characters.
Password is long enough
String Library Functions
Lua provides a rich string library for text manipulation:
Case Conversion
local text = "Hello, World!"
print("Original:", text)
print("Uppercase:", string.upper(text))
print("Lowercase:", string.lower(text))
-- Using string methods (syntactic sugar)
print("Method syntax - upper:", text:upper())
print("Method syntax - lower:", text:lower())
-- Practical example: case-insensitive comparison
local function compare_ignore_case(str1, str2)
return string.lower(str1) == string.lower(str2)
end
print("Case-insensitive comparison:")
print("'Hello' == 'HELLO':", compare_ignore_case("Hello", "HELLO"))
print("'Lua' == 'Python':", compare_ignore_case("Lua", "Python"))
Expected Output:
Original: Hello, World!
Uppercase: HELLO, WORLD!
Lowercase: hello, world!
Method syntax - upper: HELLO, WORLD!
Method syntax - lower: hello, world!
Case-insensitive comparison:
'Hello' == 'HELLO': true
'Lua' == 'Python': false
Substring Operations
local text = "Lua Programming"
-- string.sub(string, start, end)
print("Original:", text)
print("First 3 characters:", string.sub(text, 1, 3))
print("Last 4 characters:", string.sub(text, -4))
print("Characters 5-7:", string.sub(text, 5, 7))
print("From position 5 to end:", string.sub(text, 5))
-- Method syntax
print("Method syntax:", text:sub(1, 3))
-- Practical examples
local email = "[email protected]"
local at_pos = string.find(email, "@")
if at_pos then
local username = string.sub(email, 1, at_pos - 1)
local domain = string.sub(email, at_pos + 1)
print("Username:", username)
print("Domain:", domain)
end
-- Extracting file extension
local filename = "document.pdf"
local dot_pos = string.find(filename, "%.")
if dot_pos then
local name = string.sub(filename, 1, dot_pos - 1)
local extension = string.sub(filename, dot_pos + 1)
print("File name:", name)
print("Extension:", extension)
end
Expected Output:
Original: Lua Programming
First 3 characters: Lua
Last 4 characters: ming
Characters 5-7: Pro
From position 5 to end: Programming
Method syntax: Lua
Username: user
Domain: example.com
File name: document
Extension: pdf
String Search Functions
local text = "The quick brown fox jumps over the lazy fox"
-- string.find(string, pattern, start, plain)
local start_pos, end_pos = string.find(text, "fox")
print("First 'fox' found at positions:", start_pos, "to", end_pos)
-- Find with starting position
local second_fox = string.find(text, "fox", start_pos + 1)
print("Second 'fox' found at position:", second_fox)
-- Case-insensitive search using patterns
local case_insensitive = string.find(string.lower(text), string.lower("QUICK"))
print("Case-insensitive 'QUICK' found at:", case_insensitive)
-- string.match - extract matched text
local word = string.match(text, "brown")
print("Matched word:", word)
-- Extract all words
print("All words in the text:")
for word in string.gmatch(text, "%a+") do
print("-", word)
end
-- Check if string starts/ends with pattern
local function starts_with(str, prefix)
return string.sub(str, 1, #prefix) == prefix
end
local function ends_with(str, suffix)
return string.sub(str, -#suffix) == suffix
end
print("Starts with 'The':", starts_with(text, "The"))
print("Ends with 'fox':", ends_with(text, "fox"))
Expected Output:
First 'fox' found at positions: 17 to 19
Second 'fox' found at position: 42
Case-insensitive 'QUICK' found at: 5
Matched word: brown
All words in the text:
- The
- quick
- brown
- fox
- jumps
- over
- the
- lazy
- fox
Starts with 'The': true
Ends with 'fox': true
String Replacement
Replace text within strings using string.gsub
:
local text = "Hello, World! Hello, Lua!"
-- Basic replacement
local result1 = string.gsub(text, "Hello", "Hi")
print("Replace 'Hello' with 'Hi':", result1)
-- Limit number of replacements
local result2, count = string.gsub(text, "Hello", "Greetings", 1)
print("Replace first 'Hello' only:", result2)
print("Number of replacements:", count)
-- Case-insensitive replacement function
local function replace_ignore_case(str, old, new)
local lower_str = string.lower(str)
local lower_old = string.lower(old)
local result = ""
local i = 1
while i <= #str do
local found_start, found_end = string.find(lower_str, lower_old, i, true)
if found_start then
result = result .. string.sub(str, i, found_start - 1) .. new
i = found_end + 1
else
result = result .. string.sub(str, i)
break
end
end
return result
end
-- Practical examples
local code = "var x = 10; var y = 20;"
local lua_code = string.gsub(code, "var", "local")
print("Convert JS to Lua style:", lua_code)
-- Remove extra spaces
local messy_text = "This has too many spaces"
local cleaned = string.gsub(messy_text, "%s+", " ")
print("Cleaned text:", cleaned)
-- Capitalize first letter of each word
local function capitalize_words(str)
return string.gsub(str, "(%a)([%a]*)", function(first, rest)
return string.upper(first) .. string.lower(rest)
end)
end
local title = "lua programming is fun"
print("Capitalized:", capitalize_words(title))
Expected Output:
Replace 'Hello' with 'Hi': Hi, World! Hi, Lua!
Replace first 'Hello' only: Greetings, World! Hello, Lua!
Number of replacements: 1
Convert JS to Lua style: local x = 10; local y = 20;
Cleaned text: This has too many spaces
Capitalized: Lua Programming Is Fun
Lua Patterns (String Patterns)
Lua uses its own pattern matching system (similar to regex but simpler):
Basic Patterns
local text = "The phone number is 123-456-7890 and email is [email protected]"
-- Character classes
print("Find digits:", string.match(text, "%d+"))
print("Find letters:", string.match(text, "%a+"))
print("Find word characters:", string.match(text, "%w+"))
-- Pattern matching examples
local phone = string.match(text, "%d%d%d%-%d%d%d%-%d%d%d%d")
print("Phone number:", phone)
local email = string.match(text, "%w+@%w+%.%w+")
print("Email:", email)
-- Extract all numbers
print("All numbers in text:")
for number in string.gmatch(text, "%d+") do
print("-", number)
end
-- Pattern replacements
local censored = string.gsub(text, "%d", "X")
print("Censored numbers:", censored)
-- Validate patterns
local function is_valid_email(email)
return string.match(email, "^%w+@%w+%.%w+$") ~= nil
end
print("Valid emails:")
print("[email protected]:", is_valid_email("[email protected]"))
print("invalid-email:", is_valid_email("invalid-email"))
print("[email protected]:", is_valid_email("[email protected]"))
Expected Output:
Find digits: 123
Find letters: The
Find word characters: The
Phone number: 123-456-7890
Email: [email protected]
All numbers in text:
- 123
- 456
- 7890
Censored numbers: The phone number is XXX-XXX-XXXX and email is [email protected]
Valid emails:
[email protected]: true
invalid-email: false
[email protected]: true
Pattern Character Classes
-- Common character classes
local examples = {
["%a"] = "letters: Hello123",
["%d"] = "digits: abc123def",
["%w"] = "word chars: hello_world-123",
["%s"] = "whitespace: hello world",
["%p"] = "punctuation: hello, world!",
["%c"] = "control chars: hello\tworld\n",
["%x"] = "hex digits: G1A2B3C",
["%."] = "literal dot: file.txt"
}
for pattern, text in pairs(examples) do
local match = string.match(text, pattern)
print(pattern .. " in '" .. text .. "':", match or "none")
end
-- Negated character classes (uppercase)
local mixed = "Hello123World"
print("Non-letters in '" .. mixed .. "':", string.match(mixed, "%A"))
print("Non-digits in '" .. mixed .. "':", string.match(mixed, "%D"))
-- Custom character sets
local text_with_vowels = "Programming"
local vowels = string.match(text_with_vowels, "[aeiou]")
local consonants = string.match(text_with_vowels, "[^aeiou%s]")
print("First vowel:", vowels)
print("First consonant:", consonants)
String Formatting
Format strings using string.format
:
-- Basic formatting
local name = "Alice"
local age = 25
local height = 5.6
local formatted = string.format("Name: %s, Age: %d, Height: %.1f ft", name, age, height)
print("Formatted string:", formatted)
-- Number formatting
local pi = 3.14159265359
print("Pi with 2 decimals:", string.format("%.2f", pi))
print("Pi with 6 decimals:", string.format("%.6f", pi))
print("Pi in scientific notation:", string.format("%.2e", pi))
-- Integer formatting
local number = 42
print("Decimal:", string.format("%d", number))
print("Hexadecimal:", string.format("%x", number))
print("Octal:", string.format("%o", number))
print("With leading zeros:", string.format("%05d", number))
-- String formatting
local text = "Lua"
print("Default:", string.format("%s", text))
print("Right-aligned (10 chars):", string.format("%10s", text))
print("Left-aligned (10 chars):", string.format("%-10s", text))
-- Table formatting helper
local function format_table_row(...)
local args = {...}
local formatted_args = {}
for i, arg in ipairs(args) do
if type(arg) == "number" then
formatted_args[i] = string.format("%8.2f", arg)
else
formatted_args[i] = string.format("%-10s", tostring(arg))
end
end
return table.concat(formatted_args, " | ")
end
print("\nFormatted table:")
print(format_table_row("Name", "Age", "Score"))
print(string.rep("-", 35))
print(format_table_row("Alice", 25, 92.5))
print(format_table_row("Bob", 30, 87.3))
print(format_table_row("Charlie", 22, 95.8))
Expected Output:
Formatted string: Name: Alice, Age: 25, Height: 5.6 ft
Pi with 2 decimals: 3.14
Pi with 6 decimals: 3.141593
Pi in scientific notation: 3.14e+00
Decimal: 42
Hexadecimal: 2a
Octal: 52
With leading zeros: 00042
Default: Lua
Right-aligned (10 chars): Lua
Left-aligned (10 chars): Lua
Formatted table:
Name | Age | Score
-----------------------------------
Alice | 25 | 92.50
Bob | 30 | 87.30
Charlie | 22 | 95.80
Practical String Applications
Text Processing Utilities
-- String utility functions
local string_utils = {}
-- Trim whitespace
function string_utils.trim(str)
return string.match(str, "^%s*(.-)%s*$")
end
-- Split string by delimiter
function string_utils.split(str, delimiter)
local result = {}
local pattern = "(.-)" .. (delimiter or "%s") .. "?"
for part in string.gmatch(str .. delimiter, pattern) do
if part ~= "" then
table.insert(result, part)
end
end
return result
end
-- Check if string is empty or whitespace
function string_utils.is_blank(str)
return not str or string_utils.trim(str) == ""
end
-- Pad string to specified length
function string_utils.pad_left(str, length, char)
char = char or " "
local padding = length - #str
return padding > 0 and string.rep(char, padding) .. str or str
end
function string_utils.pad_right(str, length, char)
char = char or " "
local padding = length - #str
return padding > 0 and str .. string.rep(char, padding) or str
end
-- Test the utilities
local messy_text = " hello, world! "
print("Original: '" .. messy_text .. "'")
print("Trimmed: '" .. string_utils.trim(messy_text) .. "'")
local csv = "apple,banana,orange,grape"
local fruits = string_utils.split(csv, ",")
print("Split CSV:", table.concat(fruits, " | "))
print("Is blank check:")
print("Empty string:", string_utils.is_blank(""))
print("Whitespace only:", string_utils.is_blank(" "))
print("Has content:", string_utils.is_blank("hello"))
print("Padding examples:")
print("Left padded: '" .. string_utils.pad_left("42", 5, "0") .. "'")
print("Right padded: '" .. string_utils.pad_right("Name", 10, ".") .. "'")
URL and Data Processing
-- URL parsing example
local function parse_url(url)
local protocol, domain, path = string.match(url, "^(%w+)://([^/]+)(.*)$")
return {
protocol = protocol,
domain = domain,
path = path ~= "" and path or "/"
}
end
local url = "https://example.com/path/to/page"
local parsed = parse_url(url)
print("URL parsing:")
print("Protocol:", parsed.protocol)
print("Domain:", parsed.domain)
print("Path:", parsed.path)
-- Simple template system
local function render_template(template, data)
return string.gsub(template, "{{(%w+)}}", function(key)
return tostring(data[key] or "")
end)
end
local template = "Hello {{name}}, you are {{age}} years old and live in {{city}}."
local user_data = {
name = "Alice",
age = 25,
city = "New York"
}
local rendered = render_template(template, user_data)
print("Template result:", rendered)
-- Log entry parser
local function parse_log_entry(entry)
local timestamp, level, message = string.match(entry, "^(%S+)%s+(%w+):%s+(.+)$")
return {
timestamp = timestamp,
level = level,
message = message
}
end
local log_entry = "2024-01-15T10:30:45 ERROR: Database connection failed"
local parsed_log = parse_log_entry(log_entry)
print("Log parsing:")
print("Timestamp:", parsed_log.timestamp)
print("Level:", parsed_log.level)
print("Message:", parsed_log.message)
Expected Output:
Original: ' hello, world! '
Trimmed: 'hello, world!'
Split CSV: apple | banana | orange | grape
Is blank check:
Empty string: true
Whitespace only: true
Has content: false
Padding examples:
Left padded: '00042'
Right padded: 'Name......'
URL parsing:
Protocol: https
Domain: example.com
Path: /path/to/page
Template result: Hello Alice, you are 25 years old and live in New York.
Log parsing:
Timestamp: 2024-01-15T10:30:45
Level: ERROR
Message: Database connection failed
Common Pitfalls
- Pattern vs plain text: Use the 4th parameter
true
instring.find
for literal search - String immutability: Strings are immutable; operations return new strings
- Pattern escaping: Special characters in patterns need escaping with
%
- Unicode handling: Lua's string functions work with bytes, not Unicode characters
- Performance: Avoid excessive concatenation in loops; use tables and
table.concat
Checks for Understanding
- How do you create a multi-line string in Lua?
- What's the difference between
string.find
andstring.match
? - How do you replace all occurrences of a substring?
- What does the pattern
%d+
match? - How do you format a number to 2 decimal places?
Show answers
- Use double square brackets:
[[multi-line string]]
string.find
returns positions;string.match
returns the matched text- Use
string.gsub(text, old, new)
- One or more consecutive digits
- Use
string.format("%.2f", number)
Next Steps
Now that you understand string manipulation, you're ready to explore number operations and the math library for numerical computations.