谷歌 zx 脚手架模块中文文档
#!/usr/bin/env zx await $`cat package.json | grep name` let 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`, ]) let name = 'foo bar' await $`mkdir /tmp/${name}`
Bash 很棒,但是在编写脚本时,人们通常会选择更方便的编程语言。
JavaScript 是一个完美的选择,但是标准Node.js库在使用之前需要额外的麻烦。zx
包围绕child_process
提供了有用的包装器,对参数进行转义并给出合理的默认值。
安装
npm i -g zx
要求: Node 版本 >= 16.0.0
文档
在扩展名为.mjs
的文件中编写脚本,以便能够在顶层使用await
。如果您喜欢使用. js
扩展名,可以将您的脚本包装成类似于void async function () {...}()
。
将以下声明添加到您的 zx
脚本的开头:
#!/usr/bin/env zx
现在,您将能够像这样运行您的脚本:
chmod +x ./script.mjs ./script.mjs
或者通过 zx
可执行文件:
zx ./script.mjs
所有函数($
、cd
、fetch
等)都可以直接使用,无需任何导入。
或者显式导入全局变量(为了在VS代码中更好地自动完成)。
import 'zx/globals'
$`command`
使用child_process
包中的 spawn
函数执行给定字符串,并返回ProcessPromise<ProcessOutput>
。
一切都通过 ${...}
将被自动转义并引用。
let name = 'foo & bar' await $`mkdir ${name}`
不需要额外添加引号。 在 quotes 中阅读更多相关信息
如果需要,您可以传递一组参数:
let flags = [ '--oneline', '--decorate', '--color', ] await $`git log ${flags}`
如果执行的程序返回非零退出代码,将抛出 ProcessOutput
。
try { await $`exit 1` } catch (p) { console.log(`Exit code: ${p.exitCode}`) console.log(`Error: ${p.stderr}`) }
ProcessPromise
class ProcessPromise<T> extends Promise<T> { readonly stdin: Writable readonly stdout: Readable readonly stderr: Readable readonly exitCode: Promise<number> pipe(dest): ProcessPromise<T> kill(signal = 'SIGTERM'): Promise<void> }
pipe()
方法可用于重定向stdout:
await $`cat file.txt`.pipe(process.stdout)
阅读更多关于 pipelines的信息。
ProcessOutput
class ProcessOutput { readonly stdout: string readonly stderr: string readonly exitCode: number readonly signal: 'SIGTERM' | 'SIGKILL' | ... toString(): string }
函数
cd()
更改当前工作目录。
cd('/tmp') await $`pwd` // outputs /tmp
fetch()
node-fetch 包的包装。
let resp = await fetch('https://wttr.in') if (resp.ok) { console.log(await resp.text()) }
question()
readline 包的包装器。
用法:
let bear = await question('What kind of bear is best? ') let token = await question('Choose env variable: ', { choices: Object.keys(process.env) })
在第二个参数中,可以指定制表符自动完成的选项数组。
function question(query?: string, options?: QuestionOptions): Promise<string> type QuestionOptions = { choices: string[] }
sleep()
setTimeout
函数的包装。
await sleep(1000)
nothrow()
将 $
的行为更改为不在非零退出代码上引发异常。
function nothrow<P>(p: P): P
用法:
await nothrow($`grep something from-file`) // 一个 pipe() 内部: await $`find ./examples -type f -print0` .pipe(nothrow($`xargs -0 grep something`)) .pipe($`wc -l`)
如果只需要exitCode
,可以使用下一个代码:
if (await $`[[ -d path ]]`.exitCode == 0) { ... } // 相当于: if ((await nothrow($`[[ -d path ]]`)).exitCode == 0) { ... }
quiet()
更改 $
的行为以禁用详细输出。
function quiet<P>(p: P): P
用法:
await quiet($`grep something from-file`) // 不会显示命令和输出。
包
以下软件包无需导入内部脚本即可使用。
chalk
包
chalk 包。
console.log(chalk.blue('Hello world!'))
yaml
包
yaml 包。
console.log(YAML.parse('foo: bar').foo)
fs
包
fs-extra 包。
let content = await fs.readFile('./package.json')
globby
包
globby 包
let packages = await globby(['package.json', 'packages/*/package.json']) let pictures = globby.globbySync('content/*.(jpg|png)')
此外,globby可通过 glob
快捷方式获得:
await $`svgo ${await glob('*.svg')}`
os
包
os 包
await $`cd ${os.homedir()} && mkdir example`
path
包
path 包。
await $`mkdir ${path.join(basedir, 'output')}`
minimist
包
minimist 包。
作为全局常量 argv
提供。
配置
$.shell
指定使用什么 shell 。默认为 which bash
。
$.shell = '/usr/bin/bash'
或者使用一个 CLI 参数: --shell=/bin/bash
$.prefix
指定将作为所有运行命令的前缀的命令。
默认为: set -euo pipefail;
.
或者使用一个 CLI 参数:--prefix='set -e;'
$.quote
指定在命令替换期间转义特殊字符的函数。
$.verbose
指定详细程度。
默认值为 true
.
在详细模式下, zx
打印所有执行的命令及其输出。
或者使用一个 CLI 参数:--quiet
来设置 $.verbose = false
.
Polyfills
__filename
& __dirname
在 ESM 模块中,Node.js不提供 __filename
和 __dirname
全局变量。因为这样的全局变量在脚本中非常方便,所以 zx
提供了这些用于 .mjs
文件中(当使用 zx
可执行文件时)。
require()
在 ESM 模块中, require()
函数没有被定义。
zx
提供require()
函数,因此它可以与 .mjs
文件中的导入一起使用(当使用 zx
可执行文件时)。
let {version} = require('./package.json')
实验性的
zx还提供了一些实验功能。请在讨论中留下关于这些功能的反馈。
retry()
重试命令几次。将在第一次成功尝试后返回,或将在指定的尝试次数后引发。
import {retry} from 'zx/experimental' let {stdout} = await retry(5)`curl localhost`
echo()
可以接受 ProcessOutput 的console.log()
替代项。
import {echo} from 'zx/experimental' let branch = await $`git branch --show-current` echo`Current branch is ${branch}.` // 或者 echo('Current branch is', branch)
startSpinner()
启动一个简单的CLI微调器,并返回 stop()
函数。
import {startSpinner} from 'zx/experimental' let stop = startSpinner() await $`long-running command` stop()
FAQ
传递环境变量
process.env.FOO = 'bar' await $`echo $FOO`
传递值数组
如果值的数组作为参数传递给 $
,数组的项将被单独转义并通过空格连接。
例如:
let files = [...] await $`tar cz ${files}`
从其他脚本导入
通过显式导入可以使用 $
和其他函数:
#!/usr/bin/env node import {$} from 'zx' await $`date`
没有扩展名的脚本
如果脚本没有文件扩展名 (例如 .git/hooks/pre-commit
), zx
将假定它是一个 ESM 模块。
Markdown 脚本
zx
可以执行用 markdown 编写的脚本。
zx docs/markdown.md
TypeScript 脚本
import {$} from 'zx' // Or import 'zx/globals' void async function () { await $`ls -la` }()
使用 ts-node 作为一个 esm node loader.
node --loader ts-node/esm script.ts
你必须i在 package.json
中设置 "type": "module"
以及在 tsconfig.json
中设置"module": "ESNext"
。
{ "type": "module" }
{ "compilerOptions": { "module": "ESNext" } }
执行远程脚本
如果 zx
可执行文件的参数以 https://
开头,则文件将被下载并执行。
zx https://medv.io/example-script.mjs
zx https://medv.io/game-of-life.mjs
从stdin执行脚本
zx
支持从stdin执行脚本。
zx <<'EOF' await $`pwd` EOF
License
免责声明: 这不是官方支持的谷歌产品。
Quotes(引号)
将参数传递给 ${...}
不需要加引号。如果需要,将自动添加。
let name = 'foo & bar' await $`mkdir ${name}`
对于引号,zx 使用特殊的bash语法(接下来的命令是有效的bash):
mkdir $'foo & bar' $'ls' $'-la'
如果添加引号 "${name}"
,将会产生错误的命令。
如果你需要添加额外的东西,考虑把它放在花括号里。
await $`mkdir ${'path/to-dir/' + name}`
这也将正常工作:
await $`mkdir path/to-dir/${name}`
参数数组
zx
也可以在 ${...}
中接受数组或参数。数组中的项将被单独引用,并通过空格连接起来。
不要添加 .join(' ')
。
let flags = [ '--oneline', '--decorate', '--color', ] await $`git log ${flags}`
如果你已经有了一个带有数组的字符串,那么正确解析它并区分不同的参数就是你的责任了。比如像这样:
await $`git log ${'--oneline --decorate --color'.split(' ')}`
globbing 和 ~
当一切都通过${...}
将被转义,不能使用 ~
或 glob
语法。
为了实现这个目的,zx提供了 globby package.
而不能这样:
let files = '~/dev/**/*.md' // 错 await $`ls ${files}`
使用 glob
函数和 os
”包:
let files = await glob(os.homedir() + '/dev/**/*.md') await $`ls ${files}`
管道(Pipelines)
zx
支持 Node.js 流(stream),特殊的 pipe()
方法可用于重定向标准输出。
await $`echo "Hello, stdout!"` .pipe(fs.createWriteStream('/tmp/output.txt')) await $`cat /tmp/output.txt`
用 $
创建的进程从 process.stdin
获取stdin,但是我们也可以写入子进程:
let p = $`read var; echo "$var";` p.stdin.write('Hello, stdin!\n') let {stdout} = await p
管道可用于显示程序的实时输出:
$.verbose = false await $`echo 1; sleep 1; echo 2; sleep 1; echo 3;` .pipe(process.stdout)
通过管道传输stdout和stderr:
let echo = $`echo stdout; echo stderr 1>&2` echo.stdout.pipe(process.stdout) echo.stderr.pipe(process.stdout) await echo
此外,pipe()
方法可以组合 $
程序。与bash中的 |
相同:
let greeting = await $`printf "hello"` .pipe($`awk '{printf $1", world!"}'`) .pipe($`tr '[a-z]' '[A-Z]'`) console.log(greeting.stdout)
使用pipe()
和 nothrow()
:
await $`find ./examples -type f -print0` .pipe(nothrow($`xargs -0 grep ${'missing' + 'part'}`)) .pipe($`wc -l`)
Markdown 脚本
使用markdown编写脚本是可能的。zx只执行代码块。
您可以运行这个markdown 文件:
zx docs/markdown.md
await $`whoami` await $`echo ${__dirname}`
__filename
将指向 markdown.md:
console.log(chalk.yellowBright(__filename))
我们也可以在这里使用导入:
await import('chalk')
bash代码(带有 bash
或sh
语言标签)也将被执行:
VAR=$(date) echo "$VAR" | wc -c
其他代码块被忽略:
body .hero { margin: 42px; }