摘要:有时候,开发者仅仅想在启动器里快速跑一个 10 行代码的 Shell、PowerShell 或 Python 脚本(例如:一键清理本地 Docker 悬空镜像、查询指定端口占用、或者切换 Git 代理配置)。如果为了这 10 行逻辑去专门初始化一个 TypeScript 插件工程、装依赖、编译打包,未免有“杀鸡用牛刀”之嫌。本文深度拆解 ZTools 内置的
script-commands本地极客脚本引擎,揭秘如何通过 “零正则回溯头部元数据解析器” 100% 兼容@raycast.*与@ztools.*脚本注释规范,并重点攻克 Windows/macOS 跨平台脚本执行中最棘手的 “孙子进程孤儿残留(Process Tree Leak)” 与 “Windows GBK/UTF-8 双编码乱码” 两大工程难题。
一、 为什么“单文件脚本即命令(Script Commands)”是极客的刚需?
在效率启动器的能力光谱中,插件生态应该分为三个层次:
- 01第一方核心内置套件(Built-ins):如剪贴板、文件秒搜、窗口管理,随宿主预置,追求微秒级响应;
- 02社区商店扩展(Store Extensions):基于
@ztools/declarative-ui编写完整交互界面,适合分发给大众用户; - 03本地单文件脚本命令(Script Commands):零工程化配置! 用户只需在本地文件夹里新建一个
.ps1、.sh、.py或.mjs文件,在文件开头的注释里写上标题和参数定义,保存后的瞬间,它就会自动出现在 ZTools 的极简搜索框里!

二、 100% 兼容 @raycast.* 与 @ztools.* 双命名空间的元数据协议
为了让社区里现成的数千个开源 Raycast Script Commands 脚本能够 一行代码不改直接拖进 ZTools 运行,我们的脚本解析引擎同时原生识别 @raycast.* 与 @ztools.* 注释指令。
来看一个典型的跨平台脚本示例(一键清理指定端口进程):
#!/usr/bin/env bash
# Required parameters:
# @raycast.schemaVersion 1
# @raycast.title 清理端口占用进程
# @raycast.mode compact
# Optional parameters:
# @raycast.icon ⚡
# @raycast.packageName Developer Utils
# @raycast.argument1 { "type": "text", "placeholder": "输入端口号 (如 3000)", "percentEncoded": false }
PORT=$1
echo "正在查找并清理端口 $PORT ..."2.1 四种输出运行模式(Execution Modes)
根据脚本注释中的 mode 字段,ZTools 引擎会自动为其匹配四种不同的前端呈现形态:
mode 枚举值 | 界面交互行为 | 适用场景示例 |
|---|---|---|
silent | 执行后立即收起搜索窗,仅在桌面底部弹出单行 HUD 状态提示 | 一键切换系统深浅色模式、刷新 DNS 缓存 |
compact | 保持搜索框开启,在底部 Toast 状态栏仅展示脚本输出的最后一行非空文本 | 查询本机公网 IP、生成随机 UUID 并复制 |
fullOutput | 自动推入声明式 <Detail> 终端舞台,实时流式渲染完整的多行 stdout / stderr 日志 | 查看 docker ps 状态、运行构建诊断脚本 |
inline | 根据注释中的 refreshTime(如 10m / 1h),定期在后台静默运行并将首行结果直接嵌入主列表副标题中 | 实时在命令列表上显示当前服务器负载或比特币价格 |


2.2 零正则灾难回溯的头部解析器实现
为了保证扫描包含数百个脚本的目录耗时不超过 5ms,解析器只读取每个脚本文件的前 4KB(或前 50 行注释块),并采用线性逐行状态机提取元数据与最多 3 个动态输入参数(argument1 ~ argument3):
export interface ScriptCommandMetadata {
schemaVersion: number;
title: string;
mode: 'silent' | 'compact' | 'fullOutput' | 'inline';
packageName?: string;
icon?: string;
refreshTime?: string;
currentDirectoryPath?: string;
needsConfirmation?: boolean;
arguments: ScriptArgumentDef[];
}
const META_LINE_REGEX = /^(?:#|\/\/|REM\s)\s*@(?:ztools|raycast)\.([a-zA-Z0-9_]+)\s+(.+)$/;
export function parseScriptHeader(fileContent: string, filePath: string): ScriptCommandMetadata {
const lines = fileContent.split(/\r?\n/, 60); // 仅扫描前 60 行,复杂度严格 O(1)
const rawMeta: Record<string, string> = {};
const args: ScriptArgumentDef[] = [];
for (const line of lines) {
const trimmed = line.trim();
const match = META_LINE_REGEX.exec(trimmed);
if (!match) continue;
const [, key, val] = match;
if (key.startsWith('argument')) {
const parsedArg = safeParseArgumentJson(val, filePath);
if (parsedArg) args.push(parsedArg);
} else {
rawMeta[key] = val.trim();
}
}
if (!rawMeta.title) {
throw new Error(`脚本缺少必填的 @ztools.title 或 @raycast.title 声明: ${filePath}`);
}
return {
schemaVersion: Number(rawMeta.schemaVersion || 1),
title: rawMeta.title,
mode: validateMode(rawMeta.mode),
packageName: rawMeta.packageName,
icon: rawMeta.icon,
refreshTime: rawMeta.refreshTime,
needsConfirmation: rawMeta.needsConfirmation === 'true',
arguments: args,
};
}三、 攻克两大跨平台脚本执行顽疾
把注释解析出来只是万里长征第一步。真正让无数桌面工具翻车的,是脚本在调用底层 child_process.spawn 执行时的两个深坑:
顽疾 1:Windows 中文乱码地狱(UTF-8 vs CP936/GBK 双编码自适应嗅探)
在 macOS 和 Linux 上,终端标准输出(stdout)几乎 100% 是 UTF-8 编码。但在中文版 Windows 上,当你从 PowerShell 或 .bat 脚本里调用系统自带命令(如 ipconfig、netstat、taskkill、ping)时,Windows 控制台子系统(ConHost)默认吐出的字节流竟然是 GBK(代码页 CP936)!
如果宿主盲目调用 buffer.toString('utf8'),界面上输出的所有中文提示瞬间全部变成 `(Unicode 替换字符\uFFFD`)乱码!
为此,我们在脚本引擎的流式字节收集器中实现了 零损耗的 UTF-8 严格有限状态机校验 + GBK (TextDecoder('gb18030')) 自动回退解码器:
import { TextDecoder } from 'node:util';
const strictUtf8Decoder = new TextDecoder('utf-8', { fatal: true });
const gbkFallbackDecoder = new TextDecoder('gb18030', { fatal: false });
/**
* 智能双编码容错解码器:
* 优先按严格 UTF-8 验证字节序列,一旦遇到非法 UTF-8 多字节序列(如 Windows 命令吐出的 GBK 中文),
* 毫秒级自动切换至 GB18030/GBK 解码,100% 根除中文 Windows 控制台乱码!
*/
export function decodeProcessOutputBuffer(rawBuffer: Buffer): string {
if (rawBuffer.length === 0) return '';
try {
// 严格模式:若包含任何非 UTF-8 字节序列(如 GBK 编码汉字),将立即抛出 TypeError
return strictUtf8Decoder.decode(rawBuffer);
} catch {
// 自动降级为 GB18030/GBK 解码
return gbkFallbackDecoder.decode(rawBuffer);
}
}顽疾 2:脚本超时或取消后的“孙子进程孤儿泄漏”(Process Tree Termination)
假设用户写了一个 Shell 或 PowerShell 脚本 deploy.ps1,脚本内部又拉起了 ssh、python 或 ping -t 等子进程(即宿主的“孙子进程”)。
当脚本卡住超时(例如超过 30 秒),或者用户在界面上按下了“停止执行”按钮时,如果你仅仅调用 Node.js 自带的 child.kill():
- Node.js 只会杀掉最外层的壳层进程(
powershell.exe或/bin/sh); - 而脚本内部派生的孙子进程(
ping.exe/python.exe)会被操作系统过继给系统根进程,变成永远在后台狂吃 CPU 和锁死文件句柄的“孤儿僵尸进程”!
在 ZTools 的脚本执行管线中,我们实现了 跨平台进程树级联斩首(Process Tree Kill) 机制:
flowchart LR
Cancel["触发超时 (30s) 或用户按 Ctrl+C 取消"] --> Platform{"判断当前操作系统"}
Platform -->|"Windows"| WinKill["调用系统进程树清理:<br/>taskkill /PID <pid> /T /F<br/>配合 Job Object 句柄关闭"]
Platform -->|"macOS / Linux"| PosixKill["启动时设置 detached: true 独立进程组<br/>清理时向负 PID 发送信号:<br/>process.kill(-pid, 'SIGTERM' -> 'SIGKILL')"]
WinKill & PosixKill --> Clean["100% 清空整棵进程树与打开的管道句柄"]import { spawn } from 'node:child_process';
/**
* 跨平台整棵进程树级联终止,严防孙子进程残留
*/
export async function killProcessTree(pid: number, signal: NodeJS.Signals = 'SIGTERM'): Promise<void> {
if (!pid || pid <= 0) return;
if (process.platform === 'win32') {
// Windows: 使用 /T (Tree) 递归终止该 PID 及其派生的所有子孙进程,/F 强制终结
await new Promise<void>((resolve) => {
const killer = spawn('taskkill', ['/PID', String(pid), '/T', '/F'], {
windowsHide: true,
stdio: 'ignore',
});
killer.on('close', () => resolve());
killer.on('error', () => resolve());
});
} else {
// macOS / Linux: 向进程组 ID (-pid) 发送信号,一次性终结整个 Process Group
try {
process.kill(-pid, signal);
} catch {
try {
process.kill(pid, signal);
} catch {
// 进程已退出
}
}
}
}四、 总结与下期预告

通过 100% 兼容 @raycast.* / @ztools.* 元数据注释协议、UTF-8/GB18030 双编码零乱码解码器以及跨平台进程树级联管控,ZTools 让每一位开发者都能把日常积累的 PowerShell、Bash 和 Python 脚本瞬间变成触手可及的桌面效率武器。
前面我们聊了大量 Windows 下的底层黑科技,很多用 Linux 和 macOS 的极客朋友已经迫不及待了——众所周知,Raycast 官方明确表示不支持 Linux,而 Linux 现有的 Rofi / Ulauncher 在 Wayland 下饱受全局快捷键失灵、Fcitx5 中文输入法打不出字、窗口分屏失效的折磨。
- 下一篇(第 13 篇):《填补 Linux 现代效率启动器空白:Wayland 全局热键(XDG Portal)、wlr-layer-shell 悬浮层与多桌面窗口控制攻坚》,我们将带您进入硬核的 Linux 桌面协议栈深水区!(延伸阅读:《10 分钟开发一个声明式插件》 · 《Worker 进程沙箱隔离实战》)
本文关联核心插件(官网详情与使用指南)
- 极速结束进程(系统工具):跨平台整棵进程树(Job Object / Process Group)干净回收不留孤儿进程
- 本地端口占用管控(系统工具):精准识别 GBK/UTF-8 命令行输出,秒杀占用开发端口的僵尸服务
- Git 救命锦囊与命令速查(系统工具):可视化生成版本回滚、变基修复与分支清理命令,一键复制或执行
- SSH 主机与代理配置助手(系统工具):解析 ~/.ssh/config 主机清单,一键拉起终端会话或端口转发隧道
官方体验与开源共建官方网站(免费下载体验):https://www.ztools.cn (www.ztools.cn)⭐ GitHub 核心宿主开源仓库:https://github.com/ZToolsTeam/ZToolsGitee 国内镜像开源仓库:https://gitee.com/hoposky/ZToolsGitHub 插件生态开源大仓:https://github.com/ZToolsTeam/ZTools-extensions欢迎前往官网下载体验,或点击上方 GitHub / Gitee 链接为我们点亮一颗 Star ⭐,一起打造纯粹开源、免费极客的下一代跨平台效率中枢!
本文关联核心效率插件(一键直达详情)
常见问题解答 (FAQ)
如何在电脑上直接体验本文介绍的「极速结束进程」与「本地端口占用管控」功能?
前往 ZTools 官网(www.ztools.cn/download.html)免费下载并安装客户端后,按下 Alt + Space(macOS 为 Option + Space)呼出搜索框,直接输入「极速结束进程」或「本地端口占用管控」即可一键启用,无需额外繁琐配置。
本文提及的 ZTools 架构与插件功能是否完全免费?支持哪些系统?
完全免费且 100% 开源无广告。ZTools 核心宿主(GPL-3.0)与插件生态大仓(MIT)原生支持 Windows 10 / 11 (x64) 以及 macOS (Apple Silicon & Intel),个人用户永久免费享有全部核心效率工具。
使用「极速结束进程」等工具处理本地文件或剪贴板数据时,隐私安全如何保障?
ZTools 严格遵循 Local-First(本地优先)与 Privacy by Design 架构准则:所有文件索引、剪贴板历史与配置数据均 100% 存储在用户本机 SQLite 数据库中,绝不向云端上传任何个人敏感数据;跨设备同步采用 Argon2id + AES-256-GCM 端到端加密。
本文由 ZTools 官方研发团队原创首发于 ZTools 官方网站技术与效率专栏。永久原文地址:https://www.ztools.cn/articles/12-script-commands-engine-process-tree-encoding.html