app.shell - Shell 命令 API
执行 Shell 命令。支持协程内自动异步执行。
方法
app.shell.execute(command)
执行 Shell 命令(同步)。
参数:
command(string) - 要执行的命令
返回值: table
code(number) - 退出码(0 表示成功)stdout(string) - 标准输出(包含 stderr 合并输出)stderr(string) - 始终为空字符串(stderr 已合并到 stdout)
-- 简单命令
local result = app.shell.execute("ls -la")
if result.code == 0 then
app.log.info(result.stdout)
else
app.log.error(result.stderr)
end
-- 执行脚本
local result = app.shell.execute("python3 /path/to/script.py")
-- 获取命令输出
local result = app.shell.execute("which git")
local gitPath = result.stdout:gsub("\n", "")
协程内自动异步:在协程内调用时,会自动 yield 并在后台执行,对调用者透明。
-- 在协程内调用
app.thread.create(function()
-- 自动异步执行,不会阻塞主线程
local result = app.shell.execute("sleep 5 && echo done")
app.log.info(result.stdout)
end)
app.shell.executeAsync(command)
异步执行命令(不等待结果)。
参数:
command(string) - 要执行的命令
返回值: nil
-- 后台执行,不等待完成
app.shell.executeAsync("open /Applications/Safari.app")
-- 后台任务
app.shell.executeAsync("nohup /path/to/long-task.sh &")
注意事项
- 命令通过 /bin/bash -c 执行:可以使用 bash 特性
- 超时时间:默认 60 秒超时
- 路径处理:文件路径可能包含空格,需要正确引用
- 返回值:始终检查
result.code判断命令是否成功 - 编码:输出为 UTF-8 编码
示例
调用外部工具
function MyPlugin:handleConvert(context)
-- 检查工具是否安装
local result = app.shell.execute("which convert")
if result.code ~= 0 then
app.dialog.alert({title = "错误", message = "请先安装 ImageMagick"})
return
end
local files = context.selectedFiles
app.progress.show("转换中", {indeterminate = false})
for i, file in ipairs(files) do
app.progress.update(i * 100 / #files)
local output = app.path.withExtension(file, "png")
-- 使用引号处理路径中的空格
local cmd = string.format('convert "%s" "%s"', file, output)
local result = app.shell.execute(cmd)
if result.code ~= 0 then
app.log.error("转换失败: " .. result.stderr)
end
end
app.progress.hide()
app.notification.show("完成", "转换了 " .. #files .. " 个文件")
end
调用 Git 命令
function MyPlugin:handleGitStatus(context)
local dir = context.currentDirectory
-- 切换到目录执行命令
local result = app.shell.execute(string.format('cd "%s" && git status --short', dir))
if result.code ~= 0 then
app.dialog.alert({title = "错误", message = "不是 Git 仓库"})
return
end
if result.stdout == "" then
app.dialog.alert({title = "Git 状态", message = "工作目录干净"})
else
app.dialog.alert({title = "Git 状态", message = result.stdout})
end
end
使用 FFmpeg 提取音频
function MyPlugin:handleExtractAudio(context)
-- 检查 ffmpeg
local result = app.shell.execute("which ffmpeg")
if result.code ~= 0 then
app.dialog.alert({title = "错误", message = "请先安装 ffmpeg"})
return
end
for _, video in ipairs(context.selectedFiles) do
local audio = app.path.withExtension(video, "mp3")
local cmd = string.format(
'ffmpeg -i "%s" -vn -acodec libmp3lame -q:a 2 "%s"',
video, audio
)
local result = app.shell.execute(cmd)
if result.code == 0 then
app.log.info("提取成功: " .. audio)
else
app.log.error("提取失败: " .. result.stderr)
end
end
end
后台运行长任务
function MyPlugin:handleBackgroundTask(context)
local script = context.selectedFiles[1]
if not script then return end
-- 后台执行脚本
app.shell.executeAsync(string.format('"%s" > /tmp/output.log 2>&1', script))
app.notification.show("已启动", "脚本正在后台运行")
end
获取系统信息
function MyPlugin:getSystemInfo()
local info = {}
-- 获取 macOS 版本
local result = app.shell.execute("sw_vers -productVersion")
if result.code == 0 then
info.osVersion = result.stdout:gsub("\n", "")
end
-- 获取 CPU 信息
result = app.shell.execute("sysctl -n machdep.cpu.brand_string")
if result.code == 0 then
info.cpu = result.stdout:gsub("\n", "")
end
-- 获取内存大小
result = app.shell.execute("sysctl -n hw.memsize")
if result.code == 0 then
local bytes = tonumber(result.stdout)
if bytes then
info.memory = string.format("%.1f GB", bytes / 1024 / 1024 / 1024)
end
end
return info
end
处理命令输出
function MyPlugin:parseOutput(command)
local result = app.shell.execute(command)
if result.code ~= 0 then
return nil, result.stderr
end
-- 按行分割输出
local lines = {}
for line in result.stdout:gmatch("[^\n]+") do
table.insert(lines, line)
end
return lines, nil
end
function MyPlugin:handleListProcesses(context)
local lines, err = self:parseOutput("ps aux | head -20")
if err then
app.log.error(err)
return
end
app.dialog.alert({
title = "进程列表",
message = table.concat(lines, "\n")
})
end
工具方法
app.shell.quote(str)
对字符串进行 POSIX 单引号转义,用于安全拼接 shell 命令参数。
参数:
str(string) — 需要转义的字符串
返回值: string — 转义后的字符串(用单引号包裹)
-- 安全处理包含空格或特殊字符的路径
local cmd = "ls -la " .. app.shell.quote("/path/to/my file.txt")
app.shell.execute(cmd)
-- 处理包含单引号的路径
app.shell.quote("it's a file") -- "'it'\\''s a file'"