跳到主要内容
ansicode
Lua

在 Lua 中使用 ANSI 转义码

Lua 没有 `\e` 转义字面,但 `\27`(十进制)在 Lua 5.1 起的所有解释器中都可用,`\x1b`(十六进制)自 Lua 5.2 起可用。`io.write` 与 `print` 在 PUC-Rio Lua(5.1 / 5.2 / 5.3 / 5.4)、LuaJIT 以及随 Neovim、OpenResty、Redis、Wireshark 嵌入的解释器中均按字节透传。**注意**:长括号字符串 `[[...]]` **不**展开转义序列 —— 只有常规 `"..."` 字符串会展开,因此输出 ANSI 字节时必须使用双引号字符串。 人体工学方面:**ansicolors.lua**(kikito 的经典 gem)将 `colors('%{red bold}error:%{reset} permission denied')` 之类的模式字符串解析为 SGR 序列 —— 单文件依赖,可经 LuaRocks 安装或直接 vendor 拷入。**term.lua**(hoelzro 移植自 kikito 的终端控制库)覆盖非 SGR 一侧:`term.clear()`、`term.cleareol()`、`term.cursor.goto(x, y)`、`term.cursor.hide()`、备用屏 + 光标保存恢复。完整全屏 TUI 链接 **lcurses**(Lua 的 ncurses 绑定)。能力门控 —— `isatty()`、`TIOCGWINSZ`、termios 原始模式 —— 用 **LuaPosix**。

推荐库

  • ansicolors.lua

    基于模式的颜色辅助 —— `colors('%{red bold}error:%{reset} permission denied')`。模式标记 `%{<style> <color> on_<bgcolor>}` 展开为 SGR 转义序列。单文件依赖,可通过 LuaRocks 安装(`luarocks install ansicolors`)或直接 vendor 拷贝。被 `busted`、`inspect` 及众多 LÖVE 游戏用于引擎内控制台输出。

  • term.lua (hoelzro)

    终端控制辅助 —— `term.clear()`、`term.cleareol()`、`term.cursor.goto(x, y)`、`term.cursor.hide()` / `.show()`、`.save()` / `.restore()`、备用屏辅助。与 ansicolors.lua 搭配:本库处理光标 + 屏幕,ansicolors 处理 SGR。约 150 行,无原生依赖。

  • lcurses

    Lua 的 ncurses 绑定 —— 窗口、面板、菜单、表单、配色对、鼠标、原始输入。用于全屏 TUI(文件管理器、仪表板、文本模式游戏)。通过 LuaRocks 安装(`luarocks install lcurses`),构建期需 ncurses 头文件。

  • LuaPosix

    POSIX 绑定 —— `posix.unistd.isatty(1)` 用于 stdout 能力检测、`posix.termio.tcgetattr` / `tcsetattr` 切换原始模式、`posix.sys.ioctl` 配合 `TIOCGWINSZ` 查询终端尺寸。在脚本可能被管道、CI 或哑终端执行时进行能力门控所必备。

常用写法

直接 io.write —— \27(十进制,所有 Lua)或 \x1b(5.2+)
-- Lua has no \e literal. Use \27 (decimal, works in every Lua) or
-- \x1b (hex, requires Lua 5.2+). Note: long-bracket strings [[...]] do
-- NOT expand escapes — always use regular "..." strings for ANSI.

io.write("\27[1;31merror:\27[0m permission denied\n")

-- Lua 5.2+:
io.write("\x1b[1;32mok:\x1b[0m all 142 tests passed\n")
用 ansicolors.lua 实现基于模式的颜色
local colors = require 'ansicolors'

print(colors('%{red bold}error:%{reset} permission denied'))
print(colors('%{green}ok:%{reset} all 142 tests passed'))
print(colors('%{white on_blue bold} highlighted %{reset} back to normal'))
print(colors('%{yellow}deprecated:%{reset} use the new API'))
能力门控 —— LuaPosix isatty + NO_COLOR + FORCE_COLOR
-- Capability gate: NO_COLOR > FORCE_COLOR > !isatty(stdout). Wrap the
-- LuaPosix require in pcall so the script still runs on hosts without it
-- (Windows without WSL, locked-down embedded interpreters).

local ok, unistd = pcall(require, 'posix.unistd')
local is_tty = ok and unistd.isatty(1) == 1   -- 1 = STDOUT_FILENO

local function should_color()
  if os.getenv('NO_COLOR') ~= nil then return false end
  local force = os.getenv('FORCE_COLOR')
  if force and force ~= '0' then return true end
  return is_tty
end

local USE_COLOR = should_color()

local function paint(sgr, text)
  if USE_COLOR then
    return ('\27[%sm%s\27[0m'):format(sgr, text)
  end
  return text
end

print(paint('1;31', 'error:'), 'permission denied')
print(paint('1;32', 'ok:'), 'all 142 tests passed')
原地进度 —— CR + EL + 隐藏光标
-- Hide cursor for the duration of the progress loop; restore it on
-- normal exit AND on Ctrl-C. Lua's poor man's defer is an __gc finalizer
-- on a sentinel table — runs when the script exits.

io.write("\27[?25l")                              -- hide cursor

local _restore = setmetatable({}, {__gc = function()
  io.write("\27[?25h")                            -- restore cursor
end})

for i = 1, 100 do
  -- \r returns to col 1; \27[K erases to end of line
  io.write(("\r\27[K%d%% complete"):format(i))
  io.flush()
  os.execute('sleep 0.02')                         -- portable; LuaSocket has socket.sleep()
end
io.write("\n")

相关序列

其他语言