首页
/ zx 入门实战:用 JavaScript 编写 Shell 脚本的完整上手指南

zx 入门实战:用 JavaScript 编写 Shell 脚本的完整上手指南

2026-09-05 14:30:35作者:卓艾滢Kingsley

本文基于 zx 仓库官方文档 docs/getting-started.md 展开,系统讲解如何安装 zx、编写第一个脚本并直接执行、使用免导入的全局函数、理解核心模板命令 $ 及其自动转义机制、掌握 ProcessOutput 结果对象与错误处理,并结合源码印证 ProcessPromise 的底层工作方式。读完后你可以独立编写、运行并调试自己的 zx 脚本。

zx 是什么:为什么用 JavaScript 写脚本

官方文档给出的动机很直接:Bash 很好用,但脚本一旦复杂起来,很多人更倾向于使用一门更便利的编程语言。JavaScript 是个理想选择,只是 Node.js 标准库在直接用之前还有不少"仪式感"。而 zx 包恰好提供了围绕 child_process 的实用封装、参数自动转义,以及一组合理的默认值(引自 docs/getting-started.mdpackage.json 中的描述 "A tool for writing better scripts")。

一段典型的 zx 脚本长这样(官方 Overview 示例,原样保留):

#!/usr/bin/env zx

await $`cat package.json | grep name`

const branch = await $`git branch --show-current`
await $`dep deploy --branch=${branch}`

await Promise.all([
  $`sleep 1; echo 1`,
  $`sleep 2; echo 2`,
  $`sleep 3; echo 3`,
])

const name = 'foo bar'
await $`mkdir /tmp/${name}`

这段代码体现了 zx 的三个核心卖点:

  • 顶层 await:像写普通异步 JavaScript 一样按顺序执行命令;
  • ${...} 自动转义foo bar 含空格,插入 mkdir /tmp/${name} 时无需手工加引号也不会被拆成两个参数;
  • 命令即 Promise$`...`返回的ProcessPromise可以直接塞进Promise.all` 并发执行(仓库示例 examples/parallel.mjsexamples/fetch-weather.mjs 也是同样的写法)。

安装与运行环境

基本安装

npm install zx

除了 npm,docs/setup.md 还列出了更多官方安装方式:

npx zx script.js         # 不安装 zx 包直接运行脚本
npx zx@8.6.0 script.js   # 固定到某个 zx 版本

yarn add zx
pnpm add zx
bun install zx
brew install zx
docker pull ghcr.io/google/zx:8.5.0

Deno / JSR 用户则需要额外权限:

deno install -A npm:zx
# zx requires additional permissions: --allow-read --allow-sys --allow-env --allow-run

当前仓库 package.jsonversion8.9.0engines 字段声明 node >= 12.17.0docs/setup.md 进一步说明支持 Node.js >= 12.17.0、Bun >= 1.0.0、Deno 1.x/2.x、GraalVM Node.js,操作系统覆盖 Linux / macOS / Windows,并且需要一个 bash(或 PowerShell)解释器。Windows 上建议使用 WSL 或 Git Bash;如果想切换到 PowerShell,可以用 usePowerShell()usePwsh() 切换(见下文全局函数部分)。

发行渠道

docs/setup.md 将 zx 分成几个发行渠道,按需选择:

渠道 说明 安装方式
latest 主线版本,包含最新特性与改进 npm i zx
lite 精简版,适合轻量脚本,详见 docs/lite.md npm i zx@lite
dev 开发快照,含最新改动,可能不稳定 npm i zx@dev
legacy 兼容旧脚本的历史版本,只修 bug 不加新特性 npm i zx@<version>

各渠道的详细差异见 docs/versions.md

包形态

package.jsonexportsbin 字段可以看到:zx 是一个混合(hybrid)包,同时提供 ESM(build/index.js)与 CJS(build/index.cjs)入口,并导出 zxzx/globalszx/clizx/core 四个入口点;bin 字段把 zx 命令映射到 build/cli.js,这就是为什么安装后可以直接用 zx script.mjs 运行脚本。

编写并运行你的第一个脚本

文件扩展名的讲究

按文档要求,把脚本写在 .mjs 扩展名的文件中,才能在顶层直接使用 await。如果你坚持用 .js,需要用 void async function () {...}() 之类的包装函数把脚本包起来。TypeScript 同样受支持。

Shebang 与执行方式

在 zx 脚本开头加上 shebang:

#!/usr/bin/env zx

然后你有两种执行方式。

方式一:加执行权限后直接运行:

chmod +x ./script.mjs
./script.mjs

方式二:通过 zx 的 CLI 运行:

zx ./script.mjs

仓库自带的 examples/hello.mjs 是最小的可运行样例:

#!/usr/bin/env zx

await $({ verbose: true })`echo "Hello!"`

其中 $({ verbose: true })`...` 展示了"按单次调用传入选项"的写法,verbose` 会让命令执行前打印完整命令行。

CLI 常用选项

zx 命令的完整选项定义在 src/cli.tsprintUsage() 中,这里整理成速查表(均以 zx [options] <script> 形式使用):

选项 说明
--quiet 抑制所有输出
--verbose 开启 verbose 模式
--shell=<path> 指定自定义 shell 二进制
--prefix=<command> 为所有命令添加前缀
--postfix=<command> 为所有命令添加后缀
--prefer-local, -l 优先使用本地安装的包与二进制
--cwd=<path> 设置当前工作目录
--eval=<js>, -e 直接执行一段脚本
--ext=<.mjs> 指定脚本扩展名
--install, -i 运行前自动安装依赖
--registry=<URL> 指定 npm registry(默认 https://registry.npmjs.org/)
--version, -v 打印当前 zx 版本
--help, -h 打印帮助
--repl 启动交互式 REPL
--env=<path> 指定 env 文件路径

从源码 src/cli.tsreadScript() 还可以确认几个实用行为:zx -e 'await $echo hi' 可求值一段内联脚本;zx - 或空参数时从 stdin 读取脚本;参数是 http(s):// 链接时会先拉取远程脚本;.md 文件会被 transformMarkdown 转换后当作脚本执行(对应文档站中的 Markdown 模式)。

全局函数:开箱即用,无需 import

文档说明:所有函数($cdfetch 等)都无需任何 import 即可直接使用。

从源码看,这是 CLI 的功劳:src/cli.tsmain() 中执行 await import('zx/globals');而 src/globals.ts 的核心只有一行 Object.assign(globalThis, _),把主入口的全部导出挂到 globalThis 上。该文件随后用 declare global 声明了这些全局量,包括(按源码列表):

  • 命令执行与流程:$ProcessPromiseProcessOutputwithinsyncProcessCwdcdkillps
  • 配置:defaultsresolveDefaultsquietnothrowuseBashusePwshusePowerShell
  • 常用工具:echologsleepretryexpBackoffspinnerquestionstdinfetch(经 src/goods.ts 提供);
  • 文件与环境:fspathosglob / globbytempfiletempdir(别名 tmpfiletmpdir)、dotenvYAMLminimistargvparseArgvupdateArgv
  • 其他:chalkwhichquotequotePowerShellversionVERSION

如果你不是通过 CLI 运行脚本(例如用 node script.mjs 直接执行),需要显式导入全局以获得同样能力,这也是文档建议的写法(还能让 VS Code 的自动补全正常工作):

import 'zx/globals'

import 一个无导出模块只是为了触发 src/globals.ts 中的副作用赋值,这正是它能"注入全局"的机制。

深入 $ 模板命令

基本用法:同步与异步两种模式

const list = await $`ls -la`
const dir = $.sync`pwd`
  • 默认是异步模式:返回 ProcessPromiseawait 后得到 ProcessOutput
  • $.sync 是同步模式:命令执行是阻塞的,立即返回 ProcessOutput

从源码结构看(src/core.ts),$ 是一个 Proxy 包裹的函数:sync$()$.sync 的访问重定向到一个带 sync: true 默认选项的新实例,其余属性读写则落到当前 options 存储上;每次 $`cmd` 调用都会创建 ProcessPromise 并在非 halt 状态下立即 run()。默认选项由 defaults 给出(src/core.tsresolveDefaults):shell: true(默认 bash)、stdio: 'pipe'nothrow: falsequiet: falsedetached: falsepreferLocal: false,超时/终止默认信号均为 SIGTERM。此外还支持通过 ZX_ 前缀环境变量覆盖部分默认值(如 ZX_verbose=true),白名单见 src/core.ts 中的 ENV_OPTS

${...} 自动转义:不需要额外加引号

const name = 'foo & bar'
await $`mkdir ${name}`

模板字符串中 ${...} 插值的内容会被自动转义并加引号,不需要也不应该再手动加引号——手动加引号反而可能造成注入风险。这部分行为的更多规则(数组参数、glob、~ 展开、动态拼接命令)在 docs/quotes.md 中有专门讨论。

转义的具体实现可以追溯到 src/util.tsquote():空字符串渲染为 $'';由 [A-Za-z0-9_/.-+@:=,%] 构成的"安全"字符串原样输出;其余内容用 bash 的 C 风格引号 $'...' 包裹,并对反斜杠、单引号、\n\t\r 等控制字符做 C 风格转义。这正是文档所说"zx 偏好 bash 特有的 $'...' 引号方式"(见 docs/quotes.md)的底层来源。quotePowerShell() 则是 PowerShell 场景下的对应实现。

传入参数数组

需要传递一组参数时,${...} 里可以直接放数组,每个元素分别转义后以空格连接:

const flags = [
  '--oneline',
  '--decorate',
  '--color',
]
await $`git log ${flags}`

异步模式下的 thenable 等待

在异步模式下,zx 会在执行命令前等待字面量中的任何 thenable

const a1 = $`echo foo`
const a2 = new Promise((resolve) => setTimeout(resolve, 20, ['bar', 'baz']))

await $`echo ${a1} ${a2}` // foo bar baz

src/core.tsProcessPromise.run() 可以看到对应机制:若命令字符串在启动时仍不是字符串(即包含未 resolve 的 thenable),会在内部 async run(cb, ctx) 钩子中 await self.cmd 之后才真正派生进程;同步模式则不允许这种异步命令解析(build() 中直接抛出 sync mode does not allow async command resolution)。

ProcessOutput 结果对象与错误处理

结构

文档给出的 ProcessOutput 概貌:

class ProcessOutput {
  readonly stdout: string
  readonly stderr: string
  readonly signal: string
  readonly exitCode: number
  // ...
  toString(): string // Combined stdout & stderr.
  valueOf(): string  // Returns .toString().trim()
}

对照 src/core.ts 中的实际实现,ProcessOutput 继承自 Error,除上述字段外还额外提供:stdall(stdout 与 stderr 交错合并的完整输出)、duration(执行耗时,单位 ms)、okexitCode === 0 且无 error)、以及 json()text(encoding)lines(delimiter)buffer()blob(type) 等解析方法。toString() 返回 stdallvalueOf() 返回 stdall.trim()

非零退出码会抛异常

被执行的程序若返回非零退出码,ProcessOutput 会被抛出,可以用 try/catch 捕获:

try {
  await $`exit 1`
} catch (p) {
  console.log(`Exit code: ${p.exitCode}`)
  console.log(`Error: ${p.stderr}`)
}

由于 ProcessOutput extends Error,捕获到的 p 天然携带 message(由 Fail.formatExitMessage 生成的可读退出信息)、cause 等 Error 语义,可以直接打印或向上传播。

输出会被"如实地"捕获

文档强调:进程输出按原样捕获,通常程序结尾会打印一个换行符 \n。当 ProcessOutput 被当作另一个 $ 命令的参数时,zx 会使用其 stdout 并 trim 掉换行:

const date = await $`date`
await $`echo Current date is ${date}.`

结合上文 quote() 的实现理解这一点就顺理成章:${date} 插值走的是转义/取 trim 值的路径,date 命令尾部的 \n 不会把输出拆成多行,echo 最终得到的是 Current date is 2026-09-05 .... 这样干净的一行。

相关进阶阅读

围绕 $ 的更多能力,文档站内有对应页面可以深入:

相关测试位于 test/core.test.jstest/global.test.jstest/vendor.test.js,可用于验证上述行为在不同运行时下的表现;仓库同时通过 test/smoke/ 下的用例覆盖 Node/Bun/Deno/Windows 等环境。

许可证与免责声明

zx 采用 Apache-2.0 许可(见仓库根目录 LICENSE)。官方文档同时注明:This is not an officially supported Google product(这不是谷歌官方支持的产品)。

小结

按本文步骤走一遍,你应当已经掌握了:用 npm 等渠道安装 zx、以 .mjs + shebang 方式编写脚本并双通道(直接执行 / zx CLI)运行、理解全局函数从何而来、正确使用 $ 模板与自动转义、以及用 ProcessOutputtry/catch 处理命令输出和失败。这些正是 docs/getting-started.md 覆盖的核心路径;下一步建议依次阅读 docs/process-promise.mddocs/configuration.md,把管道、超时与默认值配置补全,即可写出生产级的 zx 脚本。

登录后查看全文
热门项目推荐
相关项目推荐

项目优选

收起
kernelkernel
deepin linux kernel
C
33
18
ops-transformerops-transformer
本项目是CANN提供的transformer类大模型算子库,实现网络在NPU上加速计算。
C++
1.12 K
2.72 K
kernelkernel
openEuler内核是openEuler操作系统的核心,既是系统性能与稳定性的基石,也是连接处理器、设备与服务的桥梁。
C
528
588
ops-nnops-nn
本项目是CANN提供的神经网络类计算算子库,实现网络在NPU上加速计算。
C++
906
1.83 K
pytorchpytorch
作为 Ascend for PyTorch 社区的核心组件,TorchNPU 是昇腾专为 PyTorch 打造的深度学习适配插件,使 PyTorch 框架能够直接调用昇腾 NPU,为开发者提供昇腾 AI 处理器的超强算力。
Python
854
1.34 K
docsdocs
暂无描述
Markdown
891
5.79 K
jiuwenswarmjiuwenswarm
JiuwenSwarm 是一款基于openJiuwen开发的智能AI Agent,它能够将大语言模型的强大能力,通过你日常使用的各类通讯应用,直接延伸至你的指尖。
Python
3.53 K
1.01 K
ops-mathops-math
本项目是CANN提供的数学类基础计算算子库,实现网络在NPU上加速计算。
C++
1.34 K
1.45 K
cann-learning-hubcann-learning-hub
CANN 学习中心仓,支持在线互动运行、边学边练,提供教程、示例与优化方案,一站式助力昇腾开发者快速上手。
Jupyter Notebook
988
506
AscendNPU-IRAscendNPU-IR
AscendNPU-IR是基于MLIR(Multi-Level Intermediate Representation)构建的,面向昇腾亲和算子编译时使用的中间表示,提供昇腾完备表达能力,通过编译优化提升昇腾AI处理器计算效率,支持通过生态框架使能昇腾AI处理器与深度调优
C++
540
384