下面我将详细介绍最常用和最可靠的方法。

核心方法:os.execute
这是最标准、最通用的方法,存在于所有标准的 Lua 解释器中(包括 LuaJIT),它的行为类似于 C 语言中的 system() 函数。
基本用法
os.execute 会执行一个字符串形式的 shell 命令,并返回一个状态码。
-- 执行一个简单的命令,列出当前目录下的文件(Linux/macOS)
os.execute("ls -l")
-- 执行一个简单的命令,显示当前目录下的文件列表(Windows)
os.execute("dir")
-- 执行一个带参数的命令
os.execute("echo Hello from Lua")
返回值分析
os.execute 的返回值是一个状态码,但它的解析方式在不同操作系统上可能有些复杂,一个更可靠的方法是检查返回值是否为 true(表示命令成功执行)。
-- 在 Linux/macOS 上,命令成功通常返回 true
local success = os.execute("ls > /dev/null") -- 将输出重定向到 /dev/null,避免打印
if success then
print("命令 'ls' 执行成功")
else
print("命令 'ls' 执行失败")
end
-- 在 Windows 上,成功也返回 true
local success_win = os.execute("dir > nul") -- 将输出重定向到 nul
if success_win then
print("命令 'dir' 执行成功")
else
print("命令 'dir' 执行失败")
end
获取命令的输出
os.execute 不能直接捕获命令的标准输出,它只是执行命令,并将输出直接打印到你的控制台。

如果你需要获取命令的输出并将其存储在 Lua 变量中,你需要使用输出重定向。
示例(Linux/macOS):
-- 创建一个临时文件来存储输出
local output_file = "temp_output.txt"
-- 执行命令,并将输出重定向到临时文件
os.execute("ls -l > " .. output_file)
-- 读取临时文件的内容
local file = io.open(output_file, "r")
if file then
local content = file:read("*all") -- 读取所有内容
print("捕获到的输出:")
print(content)
file:close()
-- 删除临时文件
os.execute("rm " .. output_file)
else
print("无法打开输出文件")
end
示例(Windows):
-- 创建一个临时文件来存储输出
local output_file = "temp_output.txt"
-- 执行命令,并将输出重定向到临时文件
os.execute("dir > " .. output_file)
-- 读取临时文件的内容
local file = io.open(output_file, "r")
if file then
local content = file:read("*all") -- 读取所有内容
print("捕获到的输出:")
print(content)
file:close()
-- 删除临时文件
os.execute("del " .. output_file)
else
print("无法打开输出文件")
end
注意:这种方法依赖于创建临时文件,不够优雅,并且在并发执行时可能会有问题,但对于简单的脚本来说,它通常是有效的。
(图片来源网络,侵删)
更强大的方法:使用 io.popen (适用于标准 Lua)
io.popen 打开一个进程,并将其输入或输出连接到一个 Lua 文件对象,这使得我们可以像读写文件一样读写进程的输入输出。
获取命令的输出(只读)
这是 io.popen 最常见的用途。
-- "r" 表示以只读模式打开进程的输出
local handle = io.popen('ls -l')
if handle then
print("成功打开进程,输出内容:")
-- 像读取文件一样逐行读取输出
for line in handle:lines() do
print(line)
end
-- 关闭句柄,非常重要!
handle:close()
else
print("无法执行命令或打开进程")
end
向命令输入数据(只写)
你也可以向进程的输入流写入数据。
-- "w" 表示以只写模式打开进程的输入
local handle = io.popen('sort') -- sort 命令会从标准输入读取数据并排序
if handle then
-- 向进程写入数据
handle:write("banana\n")
handle:write("apple\n")
handle:write("cherry\n")
-- 关闭句柄,这会向进程发送 EOF (End-of-File) 信号,告诉它输入结束了
handle:close()
else
print("无法执行命令或打开进程")
end
最推荐的方法:使用 sys 库 (适用于 LuaJIT)
如果你使用的是 LuaJIT,有一个专门为此设计的第三方库 sys,它比 os.execute 和 io.popen 更强大、更安全、更易用。
你需要先安装 sys 库,通常使用 luarocks:
luarocks install sys
sys.execute
sys.execute 是 os.execute 的一个更强大的替代品,它可以直接返回命令的退出码。
local exit_code = sys.execute("ls -l")
print("命令的退出码是:", exit_code)
sys.exec
sys.exec 是最强大的功能之一,它会替换当前 Lua 进程,并执行指定的命令,原 Lua 进程会终止,新命令会继承其 PID。
-- 这行代码执行后,你的 Lua 脚本就不存在了,取而代之的是一个 `ls -l` 进程
sys.exec("ls -l")
print("这行代码永远不会被执行到")
sys.process
这个模块提供了更精细的控制,可以创建子进程,并完全控制其输入、输出和错误流。
local process = sys.process.spawn("ls -l")
if process then
-- 读取标准输出
local output = process:stdout():read("*a")
print("进程输出:")
print(output)
-- 等待进程结束并获取退出码
local exit_code = process:wait()
print("进程退出码:", exit_code)
-- 关闭所有流
process:close()
else
print("无法创建进程")
end
方法对比与选择指南
| 方法 | 环境 | 获取输出 | 安全性 (防注入) | 复杂度 | 推荐场景 |
|---|---|---|---|---|---|
os.execute |
所有标准 Lua | 不能直接获取 (需重定向到文件) | 低 (有 Shell 注入风险) | 低 | 简单的命令执行,不关心输出结果,如 git pull, restart_service。 |
io.popen |
所有标准 Lua | 可以获取 (通过文件对象) | 低 (有 Shell 注入风险) | 中 | 需要捕获命令输出并进行后续处理的场景。 |
sys 库 |
LuaJIT | 可以轻松获取 (多种方式) | 高 (可安全处理参数) | 中到高 | 需要高性能、高安全性、精细控制进程的复杂场景。强烈推荐给 LuaJIT 用户。 |
重要安全提示:Shell 注入
当你执行的命令字符串包含来自用户或不可信来源的输入时,存在严重的安全风险,即 Shell 注入。
危险示例:
local user_input = "; rm -rf /" -- 恶意的用户输入
local safe_folder = "downloads"
-- 这是一个极其危险的命令!
os.execute("ls " .. user_input .. " " .. safe_folder)
user_input 如上所示,实际执行的命令会变成 ls ; rm -rf / downloads,这会尝试删除你整个根文件系统!
如何防范?
-
使用
arg数组(推荐):将命令和参数作为单独的元素传递给os.execute,这样 Lua 会直接执行命令,而不会启动一个 shell 来解析它,从而避免了大多数注入风险。-- 安全的方式 local command = "ls" local arg1 = "-l" local arg2 = "/tmp" -- 注意:Windows 下第一个参数必须是 `cmd /c` os.execute(command, arg1, arg2) -- 在 Windows 上,这会变成: os.execute({"cmd", "/c", "ls", "-l", "/tmp"}) -- 在 Linux/macOS 上,这会变成: os.execute({"ls", "-l", "/tmp"}) -
严格过滤输入:如果必须拼接字符串,请对用户输入进行严格的白名单验证,只允许特定的字符(如字母、数字、下划线、连字符等)。
-
使用
sys库:sys库的sys.process.spawn等函数在设计上就考虑了安全性,能更好地处理参数,是更安全的选择。
- 如果你只需要快速执行一个命令,不关心输出:使用
os.execute。 - 如果你使用标准 Lua,并且需要捕获命令的输出:使用
io.popen。 - 如果你使用的是 LuaJIT,并且希望代码更健壮、更安全、功能更强大:强烈推荐安装并使用
sys库,它是处理外部命令的现代、高效的最佳实践。

