zx 配置指南:深入解析$.shell、$.kill、$.defaults等全部配置项
【免费下载链接】zxA tool for writing better scripts项目地址: https://gitcode.com/GitHub_Trending/zx/zx
zx 的几乎所有行为都通过全局$对象上的属性进行配置:从指定 shell 与 spawn 实现,到控制命令前后缀、输出详略、超时信号与日志格式。本文基于官方文档 configuration,结合 src/core.ts、src/log.ts、src/util.ts 与 src/cli.ts 的源码实现,逐项说明每个配置项的默认值、取值范围、生效时机,以及对应的 CLI 参数与ZX_环境变量写法,帮助你在脚本中精确掌控 zx 的进程执行细节。
配置的作用模型:$是一个动态选项存储
理解所有配置项的前提,是理解$的底层机制。从源码看,$由 src/core.ts 中的sync$用Proxy包裹:
$.xxx = value实际写入的是AsyncLocalStorage中的当前选项存储(getStore()),而非普通对象属性;- 每次执行
$`cmd`时,getSnapshot会基于当时的 store 生成一份快照(src/core.ts),因此配置对命令的生效时点是命令被创建的那一刻; - 局部覆盖可通过
$({ timeout: 5 })这类形式完成——此时选项只在within()建立的异步上下文中生效(src/core.ts),不会污染全局$。
这意味着两种配置写法是等价的:
// 全局配置 $.verbose = true await $`long-running-cmd` // 仅对单条命令生效 await $({ verbose: true })`long-running-cmd`$.shell与$.spawn:决定命令由谁来执行
$.shell
指定执行命令所用的 shell,默认为系统 PATH 中查找到的bash(文档描述为which bash)。
$.shell = '/usr/bin/bash'对应的 CLI 参数是--shell:
zx --shell=/bin/bash script.mjs源码中$.shell的取值是string | true(src/core.ts):为字符串时直接作为spawn的 shell 路径,为true时交由 Node.js 使用默认 shell(src/core.ts)。如果$.shell为假值,命令构建阶段会直接抛出No shell is available错误(src/core.ts)。
此外,zx 提供了三个切换 shell 的辅助函数,它们会联动修改$.shell、$.prefix、$.postfix和$.quote(src/core.ts):
useBash() // 切换 bash:prefix 设为 'set -euo pipefail;',postfix 清空,quote 用 bash 版 usePwsh() // 切换 pwsh(PowerShell 7+) usePowerShell() // 切换 powershell.exe以setShell的源码可以看到,切换到 PowerShell 时会启用; exit $LastExitCode后缀并改用quotePowerShell转义函数——这正是后文$.postfix与$.quote两个配置项的典型应用场景。
$.spawn/$.spawnSync
指定底层的spawn实现,默认是 Node.js 原生的child_process.spawn与child_process.spawnSync(src/core.ts)。如果你的运行环境(如 Deno、Bun)或测试框架需要替换进程创建逻辑(例如统一 mock),可以分别设置:
import { spawn } from 'node:child_process' $.spawn = spawn $.spawnSync = spawnSync这两个实现最终透传给 src/core.ts 中exec()的spawn/spawnSync参数,sync: true的同步命令走spawnSync,异步命令走spawn。
$.prefix与$.postfix:为每条命令加前后缀
$.prefix指定会拼接在所有命令前面的 shell 片段,默认值是set -euo pipefail;(src/core.ts 中useBash()在模块加载时设置)。它保证子命令出现非零退出、未定义变量或管道失败时脚本立即中断:
$.prefix = 'set -euo pipefail;'CLI 对应--prefix:
zx --prefix='set -e;' script.mjs$.postfix则作用于命令结尾,文档给出的典型场景是兼容 PowerShell(bash 下为空字符串):
$.postfix = '; exit $LastExitCode'从源码看,两者的拼接发生在ProcessPromise.fullCmd中:prefix + cmd + postfix(src/core.ts),随后fullCmd被传入exec实际执行(src/core.ts)。一条带前后缀的完整命令形如:
set -euo pipefail; git status ; exit $LastExitCode--prefix与--postfix也可以同时通过 CLI 传入(src/cli.ts):
zx --prefix='echo foo;' --postfix='; echo bar' script.mjs$.preferLocal:优先使用本地 node_modules 中的二进制
$.preferLocal控制是否优先使用node_modules/.bin下的本地可执行文件,而不是全局安装的版本。取值有三种形态:
$.preferLocal = true // 在 $.cwd 与 process.cwd() 下查找 $.preferLocal = '/some/to/bin' // 在指定目录查找 $.preferLocal = ['/path/to/bin', '/another/path/bin'] // 多个目录$.preferLocal = true await $`c8 npm test` // 优先用本地 node_modules/.bin/c8实现上,当preferLocal为true时,查找目录是[$.cwd, process.cwd()];为字符串或数组时则展开为该目录列表(src/core.ts)。随后 preferLocalBin 会把每个目录的node_modules/.bin子目录和目录本身前置到PATH(Windows 下对应Path环境变量),原PATH值追加在末尾。这就是为什么文档示例中c8 npm test能命中本地覆盖率工具。CLI 对应--prefer-local, -l,且支持传入外部目录:zx -l=/external/node_modules/or/nm-root script.mjs。
$.quote:命令替换时的特殊字符转义
$.quote指定一个转义函数,用于模板字符串中变量插值(命令替换)时对特殊字符进行安全转义,防止注入。默认实现 quote 的转义策略是:
- 空字符串返回
$''; - 仅由
[\w/.\-+@:=,%]组成的参数原样输出; - 否则包裹成 bash 的 ANSI-C 引用
$'...',并转义反斜杠、单引号以及\f\n\r\t\v\0等特殊字符。
切换到 PowerShell 后,usePwsh()会将$.quote替换为 quotePowerShell(单引号包裹、内部单引号翻倍)。$.quote是命令构建的必备项——build()阶段若发现其为空会抛出No quote function is defined错误(src/core.ts),因此不建议随意置空,只应在需要自定义转义规则时替换实现。
$.verbose与$.quiet:输出详略控制
$.verbose(默认false)开启后,zx 会打印每条执行的命令及其输出。CLI 参数--verbose等价于$.verbose = true(src/cli.ts)。
$.quiet(默认false)则抑制所有输出,--quiet对应$.quiet = true。两者不是简单的互斥,从 isVerbose() 的实现看:
isVerbose(): boolean { return this._snapshot.verbose && !this.isQuiet() }即quiet = true会强制压制 verbose 输出。另外注意日志中 stderr 的打印条件是!isQuiet()(src/core.ts):即使不 verbose,stderr 也会透出,只有 quiet 会把它也关掉。
$.env与$.cwd:进程环境变量与工作目录
$.env
指定子进程的环境变量映射,默认是process.env。修改后对所有后续命令生效:
$.env = { ...process.env, NODE_ENV: 'test' }注意:$.env会被$.preferLocal在运行时改写(重新计算PATH),所以preferLocal场景下不要在之后依赖旧引用。
$.cwd
指定所有通过$创建进程的工作目录。这里有一个容易混淆的点:cd()函数只改变process.cwd()并同步内部的$[CWD]符号属性(src/core.ts);进程实际使用的目录取自$.cwd,未显式设置时回退到$[CWD](即process.cwd(),与原生spawn行为一致,src/core.ts):
cd('/tmp') // 只影响 process.cwd() $.cwd = '/tmp' // 显式指定所有 $ 进程的 cwdrun()阶段还会校验 cwd 是否存在,不存在时命令直接以The working directory '...' does not exist.失败,不会 spawn 进程(src/core.ts)。CLI 对应--cwd。
$.log:自定义日志函数与格式化器
$.log指定一个日志函数,默认实现位于 src/log.ts。zx 把命令生命周期拆成多种LogEntry(cmd、stdout、stderr、end、cd、fetch、retry、custom、kill,见 src/log.ts),全部经由$.log输出。典型用法是给命令打印加一个脱敏过滤器:
import {LogEntry, log} from 'zx/core' $.log = (entry: LogEntry) => { switch (entry.kind) { case 'cmd': // 例如对 cmd 打印应用自定义脱敏函数 process.stderr.write(masker(entry.cmd)) break default: log(entry) } }默认日志的定位类似 debugger,因此使用process.stderr输出。要改变输出流,覆盖$.log.output:
$.log.output = process.stdout要自定义各类条目的打印格式,定义$.log.formatters(src/log.ts 中优先取用户 formatter,找不到才回退内置):
$.log.formatters = { cmd: (entry: LogEntry) => `CMD: ${entry.cmd}`, fetch: (entry: LogEntry) => `FETCH: ${entry.url}`, }另一个实用细节:默认log函数在entry.verbose为假时直接返回(src/log.ts),所以非 verbose 模式下只有stderr与end等不带 verbose 标记的条目才会输出——这解释了 verbose/quiet 对可见输出的精确影响。
$.timeout与$.timeoutSignal:命令级超时
$.timeout指定命令执行超时时间,配套的$.timeoutSignal指定超时时发送的信号,默认SIGTERM(src/core.ts):
$.timeout = '1s' $.timeoutSignal = 'SIGKILL' await $`sleep 999`超时值的类型是Duration,可以是数字毫秒,也可以是'500ms'、'1s'、'2m'这类带单位字符串,由 parseDuration 解析(ms/s/m三种单位,非法值抛错)。触发机制在 ProcessPromise.timeout():命令start事件时注册setTimeout,到期后调用kill($.timeoutSignal),命令结束后自动清理定时器。超时属于"命令失败",ProcessOutput的signal字段会记录实际发送的信号。
$.delimiter:输出切分分隔符
$.delimiter指定把命令输出切分为行的分隔符,默认/\r?\n/(换行符或回车+换行,src/core.ts)。处理含特殊字符的文件名时,常用 null 字符作为分隔:
$.delimiter = /\0/ await $`find ./ -type f -print0 -maxdepth 1`该分隔符影响ProcessOutput.lines()与对ProcessOutput的for...of遍历(src/core.ts)以及ProcessPromise的异步迭代(src/core.ts)。优先级是:单条命令通过o.lines(/\0/)传入的参数 >$.delimiter> 默认/\r?\n/。
$.kill与$.killSignal:进程终止策略
$.kill指定一个 kill 函数,默认实现是"半优雅终止":基于ps.tree()递归收集进程树后代,逐一process.kill(pid, signal),再尝试-pid(进程组)与单 pid 兜底(src/core.ts);在 Windows 上会优先taskkill /pid <pid> /t /f。$.killSignal默认SIGTERM,用于ProcessPromise.kill()未指定信号时的兜底(src/core.ts)。
默认的进程树扫描在某些平台上可能不够彻底,可以替换为更精细的实现,例如tree-kill:
import treekill from 'tree-kill' $.kill = (pid, signal = 'SIGTERM') => { return new Promise((resolve, reject) => { treekill(pid, signal, (err) => { if (err) reject(err) else resolve() }) }) }注意区分两个信号:$.killSignal管kill()调用,$.timeoutSignal管超时终止,两者可独立设置。
$.defaults:默认配置全景与ZX_环境变量
$.defaults持有所有默认配置值,当$上的对应选项未指定时回退使用。文档给出的完整默认值清单如下(defaults 的对应实现):
$.defaults = { cwd: process.cwd(), env: process.env, verbose: false, quiet: false, sync: false, shell: true, prefix: 'set -euo pipefail;', // for bash postfix: '; exit $LastExitCode', // for powershell nothrow: false, stdio: 'pipe', // equivalent to ['pipe', 'pipe', 'pipe'] detached: false, preferLocal: false, spawn: childProcess.spawn, spawnSync: childProcess.spawnSync, log: $.log, kill: $.kill, killSignal: 'SIGTERM', timeoutSignal: 'SIGTERM', delimiter: /\r?\n/, }这里有两点源码层面的补充:
prefix: 'set -euo pipefail;'与postfix: '; exit $LastExitCode'分属 bash 与 PowerShell 两套约定,实际由模块加载时的useBash()决定启用哪一种(src/core.ts)——默认是 bash 前缀,postfix 为空。- 默认值并非一成不变:
resolveDefaults会用ZX_前缀的环境变量覆盖其中白名单内的选项(src/core.ts)。当前支持的环境变量键为(src/core.ts):
ZX_CWD ZX_PREFER_LOCAL ZX_DETACHED ZX_VERBOSE ZX_QUIET ZX_TIMEOUT ZX_TIMEOUT_SIGNAL ZX_KILL_SIGNAL ZX_PREFIX ZX_POSTFIX ZX_SHELL例如ZX_VERBOSE=true ZX_SHELL='/bin/bash' zx script.mjs等价于zx --verbose --shell=/bin/bash script.mjs;布尔值通过 parseBool 解析('true'→true,'false'→false,其他非空字符串视为true)。ZX_前缀的选项名会经toCamelCase转换(ZX_TIMEOUT_SIGNAL→timeoutSignal),在 CI 里配置尤为方便:
steps: - name: Run script run: zx script.mjs env: ZX_VERBOSE: true ZX_SHELL: '/bin/bash'CLI 侧同样在参数解析阶段调用resolveDefaults合并这些环境变量(src/cli.ts),因此优先级可以理解为:命令行显式参数 > 脚本内$.xxx赋值 >ZX_环境变量 > 内置默认值。
CLI 参数速查
与配置项直接相关的 CLI 参数(完整列表见 printUsage 与 cli.md):
| 参数 | 等价配置 | 说明 |
|---|---|---|
--shell=<path> | $.shell | 自定义 shell 二进制 |
--prefix=<command>/--postfix=<command> | $.prefix/$.postfix | 给每条命令加前后缀 |
--verbose/--quiet | $.verbose = true/$.quiet = true | 输出详略 |
--prefer-local, -l | $.preferLocal | 可用--prefer-local=<dir>指定外部目录 |
--cwd=<path> | $.cwd | 设置当前目录 |
--env=<path> | 加载 dotenv 文件后重新resolveDefaults | 环境变量文件,配合--cwd解析相对路径 |
其中--env的行为值得注意:src/cli.ts 先按$.cwd ?? process.cwd()解析 env 文件路径,用 dotenv 载入后再执行一次resolveDefaults(),因此 env 文件中写入的ZX_变量也会参与默认值解析。
小结
zx 的配置体系围绕"全局$+ 按命令选项快照"两级展开:$.shell/$.spawn/$.kill决定进程如何创建与终止,$.prefix/$.postfix/$.quote决定命令如何拼装,$.verbose/$.quiet/$.log/$.delimiter决定输出如何呈现,$.timeout/$.env/$.cwd/$.preferLocal决定执行上下文,而$.defaults与ZX_环境变量提供了不改脚本的最后一层覆盖能力。理解 src/core.ts 中快照(getSnapshot)与fullCmd的拼接顺序后,上述每一项配置在何时生效、与谁相互影响,都可以从源码直接验证。
【免费下载链接】zxA tool for writing better scripts项目地址: https://gitcode.com/GitHub_Trending/zx/zx
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考