Работа со строками в Lua: основные функции библиотек string и utf8
Опубликовано:
В этой статье на примерах рассматриваются наиболее используемые строковые функции, а также особенности языка паттернов Lua.
Работа со строками в Lua имеет свои особенности: отсутствует символьный тип, используется 1-индексация, иммутабельность (неизменчивость) с агрессивным интернированием (хранится только одна копия каждой уникальной строки в памяти), собственный механизм сопоставления шаблонов (Patterns) вместо классического Regex.
В Lua строковые методы можно вызывать как через библиотеку (string.*), так и через двоеточие от самого экземпляра строки (ООП-синтаксис), так как у всех строк имеется общая метаблица:
local text = 'Gamer' print(string.lower(text)) -- gamer print(text:upper()) -- GAMER
Длина строки:
local s = 'hello' print(#s) -- 5 print(s:len()) -- 5
В Lua строки соединяются через операцию .., но такая конкатенация в цикле снижает производительность, поэтому вместо нее обычно используют table.concat.
В Lua строки не только индексируются с 1, но нет и привычного синтаксиса для получения символа через индекс (s[i]). Вместо этого используется функция string.sub для взятия срезов.
local s = 'hello world' print(s:sub(7)) -- world print(s:sub(1, 1)) -- h print(s:sub(3, 5)) -- llo print(s:sub(-2)) -- ld
Функция string.format — форматирование
Функция string.format в Lua используется для форматирования строк и работает практически так же, как sprintf в Си.
-- Ограничение знаков после запятой (округление) local pi = 3.14159265 print(string.format('%.2f', pi)) --> '3.14' (две цифры после запятой) print(string.format('%.4f', pi)) --> '3.1416' (четыре цифры с округлением) -- Добавление ведущих нулей (полезно для дат и времени) local day, month, year = 5, 9, 2026 print(string.format('%02d/%02d/%d', day, month, year)) --> '05/09/2026' -- Выравнивание чисел пробелами (создание таблиц) print(string.format('%4d', 7)) --> ' 7' (ширина поля 4 символа) print(string.format('%4d', 123)) --> ' 123' -- Перевод в шестнадцатеричную (Hex) систему local r, g, b = 255, 165, 0 -- %02X выведет заглавные hex-символы с ведущим нулем, если число меньше 16 print(string.format('#%02X%02X%02X', r, g, b)) --> '#FFA500' -- Перевод в восьмеричную (Octal) систему print(string.format('%o', 8)) --> '10' -- Вставка строк и подгонка по ширине local name = 'Иван' print(string.format('Привет, %s!', name)) --> 'Привет, Иван!' -- Выравнивание строки по правой и левой стороне print(string.format('|%10s|', 'тест')) --> '| тест|' (по правому краю) print(string.format('|%-10s|', 'тест')) --> '|тест |' (по левому краю)
Функция string.find — поиск
Функция string.find в Lua предназначена для поиска подстроки или паттерна (регулярного выражения Lua) внутри базовой строки. Функция принимает на вход исходную строку, искомую подстроку (или паттерн), и необязательные параметры: индекс начала поиска и флаг простого поиска.
В случае успешного поиска функция возвращает два индекса (начало и конец совпадения). Если в паттерне использовались захваты (захватывающие группы в круглых скобках), функция вернет их значения третьим, четвертым и последующими аргументами. Если подстрока не найдена, функция возвращает nil.
local text = 'Python Lua JS Java C Pascal' if string.find(text, 'Lua') then print('В списке есть Lua!') else print('Не найдено.') end
По умолчанию поиск идет с 1-го символа. С помощью третьего аргумента можно указать, откуда начинать поиск (например, чтобы найти второе вхождение слова).
local text = 'one, two, three, one, ten' local first_start, first_end = string.find(text, 'one') print(first_start) --> 1 -- Ищем следующее вхождение, начиная поиск сразу за концом первого local second_start, second_end = string.find(text, 'one', first_end + 1) print(second_start) --> 18
По умолчанию string.find воспринимает спецсимволы (., %, *, -, ? и др.) как элементы паттернов Lua. Если нужно найти строку "как есть", передают true в качестве четвертого аргумента. Перед ним обязательно указывается начальный индекс (обычно 1).
local text = 'The file has been 100% updated.' -- Без флага plain: % воспринимается как управляющий символ, будет ошибка или неверный поиск -- print(string.find(text, '100%')) -- С флагом plain = true: ищет точный текст '100%' local s, e = string.find(text, '100%', 1, true) print(s, e) --> 19 22
Если в паттерне есть круглые скобки, string.find извлечет совпадения и вернет их после основных индексов.
local log = 'Error: code 404, page not found' -- Паттерн '%d+' ищет одну или более цифр подряд, круглые скобки захватывают их local start_idx, end_idx, error_code = string.find(log, 'code (%d+)') print(start_idx, end_idx) --> 8 15 print(error_code) --> 404 (это строка)
Функция string.gsub — замена
Функция string.gsub (от global substitution) в Lua предназначена для глобального поиска и замены подстрок или паттернов в строке.
Функция принимает на вход исходную строку, паттерн для поиска и правило замены (строку, таблицу или функцию). Также можно передать необязательный четвертый аргумент — максимальное количество замен. Всегда возвращает два значения: новую строку с выполненными заменами, общее количество сделанных замен (целое число).
local text = 'Банан — это желтый фрукт. Банан вкусный.' -- Заменяем все слова 'Банан' на 'Апельсин' local result, count = string.gsub(text, 'Банан', 'Апельсин') print(result) --> 'Апельсин — это желтый фрукт. Апельсин вкусный.' print(count) --> 2 (было сделано две замены)
В строке замены можно использовать специальные маркеры %1, %2 и т.д., чтобы подставить текст, который был захвачен круглыми скобками в паттерне. Маркер %0 означает весь найденный фрагмент.
local date = '2026-10-23' -- Меняем формат даты с ГГГГ-ММ-ДД на ДД.ММ.ГГГГ -- (%d+) захватывает группы цифр local formatted = string.gsub(date, '(%d+)-(%d+)-(%d+)', '%3.%2.%1') print(formatted) --> '23.10.2026'
Если третий аргумент — таблица (словарь), то string.gsub ищет совпадение (или первый захват паттерна) в качестве ключа этой таблицы. Если ключ найден, он заменяется на значение из таблицы. Если ключа нет, текст остается без изменений.
local vars = { name = 'Алексей', role = 'Администратор' } local template = 'Пользователь $name зашел в систему как $role.' -- Ищем конструкцию $переменная, где имя переменной состоит из букв (%a+) local result = string.gsub(template, '%$(%a+)', vars) print(result) --> 'Пользователь Алексей зашел в систему как Администратор.'
Если в качестве третьего аргумента передана функция, она будет вызываться при каждом совпадении. В функцию передаются все захваченные подстроки. То, что функция вернет (должна быть строка или число), станет заменой. Если функция возвращает nil или false, замена для этого фрагмента не происходит.
local text = 'Товар стоит 100 долларов и 50 долларов доставка.' -- Функция увеличивает каждое найденное число в 2 раза local double_prices = string.gsub(text, '%d+', function(price) return tonumber(price) * 2 end) print(double_prices) --> 'Товар стоит 200 долларов и 100 долларов доставка.'
Функция string.gmatch — итератор совпадений
Функция string.gmatch в Lua предназначена для последовательного поиска всех совпадений по паттерну в строке. В отличие от других функций, она возвращает не строки или индексы, а функцию-итератор, которую чаще всего используют в циклах for.
Чтобы пройти по каждому символу, используют шаблон "." (любой символ):
local s = 'hello' -- string.gmatch возвращает функцию-итератор for char in string.gmatch(s, '.') do print(char) end
Разбиение строки на слова:
local text = 'one, two, three!' -- Паттерн '%a+' находит каждое слово по очереди for word in string.gmatch(text, '%a+') do print(word) end
one two three
Извлечение пар "ключ = значение" (использование захватов): если в паттерне несколько круглых скобок, gmatch возвращает столько же значений за одну итерацию.
local config = 'width=1920; height=1080; fullscreen=true;' -- Ищем имя ключа (%w+) и его значение (%w+) вокруг знака равенства for key, value in string.gmatch(config, '(%w+)=(%w+)') do print('Ключ:', key, '. Значение:', value) end
Ключ: width . Значение: 1920 Ключ: height . Значение: 1080 Ключ: fullscreen . Значение: true
Разделение строки по разделителю:
local path = '/usr/local/bin/lua' -- [^/]+ означает 'любые символы, кроме косой черты, один или более раз' for folder in string.gmatch(path, '[^/]+') do print(folder) end
usr local bin lua
Паттерны Lua и функция string.match
Функция string.match и механизм паттернов (patterns) — это встроенный в Lua инструмент для поиска и извлечения текста. В отличие от многих других языков программирования, Lua не использует стандартные регулярные выражения (POSIX или PCRE). Вместо этого в нем реализована собственная, более легковесная и быстрая система паттернов.
Основные классы символов (вместо обратного слэша используется знак процента %):
%a— любая буква.%d— любая цифра.%w— любая буквенно-цифровая клавиша (буква или цифра).%s— любой пробельный символ (пробел, табуляция, перенос строки).%p— знаки пунктуации (.,,,!,?и т.д.).%x— шестнадцатеричные цифры..(точка) — вообще любой символ.
Инверсия класса: если написать букву в верхнем регистре, класс станет работать от противного. Например, %D — любой символ, кроме цифры, а %S — любой непробельный символ.
Квантификаторы (повторения):
+— 1 или более повторений (жадный поиск).*— 0 или более повторений (жадный поиск).-— 0 или более повторений (ленивый поиск — находит минимально возможную строку. Очень полезно!).?— 0 или 1 вхождение.
Якоря и экранирование:
^— начало строки (если стоит в самом начале паттерна).$— конец строки (если стоит в самом конце паттерна).- Экранирование: Чтобы найти сам управляющий символ (например, точку, плюс или знак процента), перед ним нужно поставить
%. Например:%.— это просто точка,%%— знак процента.
В отличие от string.find, которая возвращает индексы местонахождения текста, string.match возвращает сам найденный текст. Если в паттерне нет круглых скобок, функция возвращает всю подстроку, которая подошла под паттерн. Если в паттерне есть круглые скобки (захваты), функция возвращает только то, что попало внутрь этих скобок. Если ничего не найдено, возвращается nil.
Простой поиск слова по шаблону:
local text = 'Номер заказа: ID-48291' -- Ищем 'ID-' и все цифры после него local result = string.match(text, 'ID%-%d+') print(result) --> 'ID-48291'
Извлечение конкретной части строки (с помощью захвата):
local email = 'Игрок: user123@gmail.com' -- Нам нужно только имя пользователя до собаки. -- (%w+) захватит буквы и цифры, стоящие перед @ local username = string.match(email, '(%w+)@') print(username) --> 'user123'
Множественный захват:
local time = 'Текущее время 14:35:02' -- Захватываем часы, минуты и секунды по отдельности local hrs, mins, secs = string.match(time, '(%d+):(%d+):(%d+)') print(hrs, mins, secs) --> 14 35 02
Использование ленивого квантификатора -. Представим, что нужно вырезать текст внутри HTML-тегов <div>...</div>:
local html = '<div>Первый</div><div>Второй</div>' -- Жадный поиск `.*` захватит всё от первого <div> до последнего </div> print(string.match(html, '<div>(.*)</div>')) --> 'Первый</div><div>Второй' (Неверно!) -- Ленивый поиск `.-` остановится на самом ближайшем закрывающем теге print(string.match(html, '<div>(.-)</div>')) --> 'Первый' (Правильно)
Библиотека utf8 для многобайтовых символов
В Lua модуль string работает с байтами, а не символами. Поскольку каждая русская буква занимает 2 байта, string.len("Привет") вернет 12, а не 6. Однако в последних версиях Lua имеется встроенная библиотека utf8, которая предоставляет функции для корректной работы со строками в кодировке UTF-8.
Функция utf8.len считает именно символы (буквы), а не байты:
local str = 'Привет' print(string.len(str)) -- 12 print(utf8.len(str)) -- 6
Функция utf8.codes используется в цикле for. Она возвращает байтовую позицию символа (pos) и его числовой Unicode-код (code).
local str = 'Мир' for pos, code in utf8.codes(str) do -- utf8.char() превращает числовой код обратно в символ print(string.format('Позиция байта: %d, Символ: %s, Код: %d', pos, utf8.char(code), code)) end
Позиция байта: 1, Символ: М, Код: 1052 Позиция байта: 3, Символ: и, Код: 1080 Позиция байта: 5, Символ: р, Код: 1088
Так как string.sub работает с байтами, напрямую передавать туда номера символов нельзя. Нужно сначала найти байтовое смещение с помощью utf8.offset.
local function utf8_sub(str, start_char, end_char) -- Находим байтовое смещение для начального символа local start_byte = utf8.offset(str, start_char) if not start_byte then return '' end -- Находим байтовое смещение для конечного символа local end_byte if end_char then -- Смещение конца — это начало СЛЕДУЮЩЕГО символа минус 1 байт local next_byte = utf8.offset(str, end_char + 1) end_byte = next_byte and (next_byte - 1) or #str else end_byte = #str end return string.sub(str, start_byte, end_byte) end local text = 'Программирование' print(utf8_sub(text, 1, 4)) -- Выведет: Прог print(utf8_sub(text, 5, 11)) -- Выведет: раммиро
Если строка повреждена (например, обрезана посреди байтов русской буквы), utf8.len вернет nil, а вторым аргументом — позицию ошибки.
local valid_str = 'Тест' local broken_str = string.sub('Тест', 1, 3) -- Срезали половину второй буквы print(utf8.len(valid_str)) -- Выведет: 4 print(utf8.len(broken_str)) -- Выведет: nil 3 (ошибка на 3-м символе)