Lua — компактный язык, который почти не заметен в десктопных приложениях, но управляет ими изнутри. На нём пишут конфигурации Neovim, скрипты для игровых движков, правила обработки запросов в nginx и автоматизацию в embeddable-сценариях. Разберём синтаксис, таблицы и метатаблицы, модули, работу с файлами, встраивание в C и инструменты проверки кода.
Почему Lua до сих пор актуален
Язык появился как встраиваемый инструмент для приложений на C и таким остался. Его интерпретатор весит сотни килобайт, сборщик мусора работает без длинных пауз, а сам движок легко линкуется в чужой процесс. Отсюда и распространение: вместо того чтобы писать собственный скриптовый движок, разработчик встраивает Lua и получает готовый механизм расширений.
Синтаксис намеренно маленький: нет классов, нет исключений, нет богатой стандартной библиотеки. Всё это заменяют таблицы, функции и метатаблицы. Такой минимализм поначалу удивляет, зато язык изучается за вечер, а поведение программы остаётся предсказуемым.
- Интерпретатор для Linux, Windows и встраиваемых платформ.
- Целочисленная арифметика в ветке 5.4 наряду с числами с плавающей точкой.
- Модули подключаются через
requireи обычные файлы. - JIT-версия LuaJIT даёт скорость, близкую к компилируемому коду.
Установка и проверка версии
В Debian и Ubuntu пакет называется lua5.4, в Fedora и Arch — просто lua. Менеджер пакетов luarocks ставится отдельно и нужен для библиотек.
sudo apt install lua5.4 luarocks
lua5.4 -v
luarocks --version
lua5.4 -e 'print(_VERSION, math.maxinteger)'
Если в системе несколько версий, вызывайте интерпретатор с явным суффиксом, иначе lua может указывать на 5.1 из зависимостей другого пакета. Для скриптов с шебангом используйте #!/usr/bin/env lua5.4.
Основы синтаксиса
Переменные по умолчанию глобальные, поэтому local — не украшение, а необходимость: он ограничивает область видимости и ускоряет доступ. Функции возвращают несколько значений, а select("#", ...) позволяет узнать число аргументов.
local function greet(name, ...)
local extra = select("#", ...)
return string.format("Привет, %s! Лишних аргументов: %d", name, extra)
end
print(greet("мир", 1, 2, 3))
Строки неизменяемы, конкатенация выполняется оператором .., длина — #. В цикле накопление большой строки через .. неэффективно: складывайте фрагменты в таблицу и вызывайте table.concat.
Таблицы и метатаблицы
Таблица — единственная структура данных, и одновременно массив, словарь и объект. Индексация с единицы, а не с нуля, — частая причина ошибок у новичков.
local user = { name = "admin", role = "ops", tags = { "ssh", "sudo" } }
for i, tag in ipairs(user.tags) do print(i, tag) end
for key, value in pairs(user) do print(key, value) end
table.insert(user.tags, "systemd")
print(#user.tags, user.name)
Метатаблица задаёт поведение таблицы при сложении, обращении к отсутствующему полю и вызове. Через __index строят объекты с наследованием, через __tostring — читаемый вывод, через __newindex — контроль записи.
local Account = {}
Account.__index = Account
function Account.new(owner, balance)
return setmetatable({ owner = owner, balance = balance or 0 }, Account)
end
function Account:deposit(sum)
self.balance = self.balance + sum
return self
end
local acc = Account.new("team", 100):deposit(50)
print(acc.owner, acc.balance)
Модули и структура проекта
Модуль — это файл, который возвращает таблицу. require кеширует результат, поэтому повторный вызов не перечитывает файл. Пути поиска задаются в package.path.
-- lib/text.lua
local M = {}
function M.trim(s) return (s:gsub("^%s*(.-)%s*$", "%1")) end
return M
-- main.lua
package.path = package.path .. ";./lib/?.lua"
local text = require("text")
print(text.trim(" строка "))
Держите модули в отдельном каталоге и не полагайтесь на текущий рабочий каталог: при запуске из systemd он может отличаться. Абсолютный путь, вычисленный от arg[0], надёжнее.
Автоматизация в командной строке
Lua хорошо подходит для небольших обработчиков: разобрать лог, переименовать файлы, проверить формат. Аргументы доступны в глобальной таблице arg, файлы читаются через io.lines.
local path = assert(arg[1], "укажите путь к файлу")
local count, errors = 0, 0
for line in io.lines(path) do
count = count + 1
if line:find("ERROR", 1, true) then errors = errors + 1 end
end
print(string.format("%s: строк %d, ошибок %d", path, count, errors))
Запуск: lua5.4 count.lua /var/log/syslog. Для обхода каталога используйте io.popen("find ...") либо библиотеку lfs из luarocks — стандартная библиотека работу с каталогами не покрывает.
Встраивание в приложение на C
Встраивание сводится к четырём шагам: создать состояние, открыть библиотеки, загрузить скрипт, закрыть состояние. Ошибки возвращаются как код, а текст лежит на вершине стека.
#include
#include
#include
#include
int main(void) {
lua_State *L = luaL_newstate();
luaL_openlibs(L);
if (luaL_dofile(L, "script.lua") != LUA_OK) {
fprintf(stderr, "%s\n", lua_tostring(L, -1));
lua_close(L);
return 1;
}
lua_close(L);
return 0;
}
Сборка: cc embed.c -o embed $(pkg-config --cflags --libs lua5.4). Для вызова функции из C используют lua_getglobal, lua_pushnumber и lua_pcall. Всегда проверяйте код возврата: молча проигнорированная ошибка скрипта превращается в загадочное поведение приложения.
Где Lua уже работает
В Neovim файл init.lua заменяет старый vimrc: настройки, плагины и автокоманды пишутся на Lua и выполняются быстрее, чем Vimscript. В nginx с модулем OpenResty блок content_by_lua_block позволяет обрабатывать запрос без обращения к бэкенду. В Redis скрипты EVAL выполняют несколько команд атомарно на стороне сервера.
Общее правило: Lua отлично подходит для логики расширения, но не для тяжёлых вычислений в основном потоке. Если скрипт считает секундами, вынесите работу в отдельный процесс или используйте LuaJIT.
Инструменты качества
Синтаксис проверяется компилятором без запуска: luac -p file.lua. Линтер luacheck находит неиспользуемые переменные и обращения к глобалам, а stylua приводит форматирование к единому виду. Тесты удобно писать на busted.
luarocks install luacheck
luacheck . --no-max-line-length
luac5.4 -p script.lua
lua5.4 -e 'local ok, err = pcall(dofile, "script.lua"); print(ok, err)'
Для отладки используйте xpcall с обработчиком и debug.traceback: стандартный error без трассировки показывает только сообщение. В production-скриптах полезно оборачивать точку входа целиком, чтобы ошибка не завершала процесс молча.
Итог
Lua стоит изучать не как замену Python, а как язык расширений и небольших сценариев. Таблицы, метатаблицы и модули дают достаточно гибкости, а крошечный интерпретатор без проблем живёт внутри чужого приложения. Освойте local, pairs и require, добавьте в проект luacheck и stylua — и язык перестанет казаться экзотикой. Для автоматизации он часто оказывается самым коротким путём.