大家好,很高兴又见面了,我是"高级前端进阶",由我带着大家一起关注前端前沿、深入前端底层技术,大家一起进步,也欢迎大家关注、点赞、收藏、转发!
1.什么是 zx
创建 shell 脚本(由 Bash 或 zsh 等 shell 执行的脚本)可能是自动执行重复任务的通用方法。 而 Node.js 似乎是编写 shell 脚本的理想选择,因为其提供了许多核心模块,并允许导入选择的任何库,同时还能访问 JavaScript 提供的语言特性和内置函数。
但如果尝试编写一个在 Node.js 下运行的 shell 脚本,可能会发现其并不像希望的那么顺利,比如:需要为子进程编写特殊处理,处理转义命令行参数,然后最终搞乱 stdout(标准输出)和 stderr(标准错误)。 同时,还不直观且会使 shell 脚本编写变得非常尴尬。
Bash shell 脚本语言是编写 shell 脚本的流行选择,开发者无需编写代码来处理子进程,并且其具有用于处理 stdout 和 stderr 的内置语言功能。 但用 Bash 编写 shell 脚本也不是那么容易,比如:语法可能非常混乱,使得逻辑实现或处理提示用户输入之类的代码变得非常困难。
Google 的 zx 包提供了封装子进程创建以及这些进程中 stdout 和 stderr 处理的函数,即本质上是 child_process 的包装器,包装器最重要的就是 $ 函数。
#!/usr/bin/env zx
await Google 开源 zx,用 async/await 编写 shell 脚本 - 今日头条 cat package.json | grep name`
let branch = await Google 开源 zx,用 async/await 编写 shell 脚本 - 今日头条 git branch --show-current`
await Google 开源 zx,用 async/await 编写 shell 脚本 - 今日头条 dep deploy --branch=${branch}`
await Promise.all([
Google 开源 zx,用 async/await 编写 shell 脚本 - 今日头条 sleep 1; echo 1`,
Google 开源 zx,用 async/await 编写 shell 脚本 - 今日头条 sleep 2; echo 2`,
Google 开源 zx,用 async/await 编写 shell 脚本 - 今日头条 sleep 3; echo 3`,
])
let name = 'foo bar'目前 zx 在 Github 上通过 Apache-2.0 协议开源,有超过 40k 的 star、1k 的 fork、7.1k 的项目依赖量、代码贡献者 50+、妥妥的前端优质开源项目。
2.如何使用 zx
安装和使用 zx
使用 zx 首先需要安装:
npm install zx
// npm
deno install -A npm:zx
// deno
brew install zx
// brew
接着将脚本写入扩展名为 .mjs 的文件中,以便在顶层使用 wait。 如果更喜欢 .js 扩展名,可以将脚本包装在 void async function () {...}() 之类的内容中。
然后将以下 shebang 添加到 zx 脚本的开头:
#!/usr/bin/env zx
此时可以按照下面方式运行脚本:
chmod +x ./script.mjs
./script.mjs
// 或者按照 cli
zx ./script.mjs
所有函数($、cd、fetch 等)都可以立即使用,无需任何导入。 或者显式导入全局变量,以便在 VS Code 中更好地自动完成。
import 'zx/globals'
$command
使用 spawn func 执行给定的命令并返回 ProcessPromise, 所有通过 ${...} 传递的内容都会被自动转义并引用。
const name = 'foo & bar'
await Google 开源 zx,用 async/await 编写 shell 脚本 - 今日头条 mkdir ${name}`无需添加额外的引号,如果需要,可以传递参数数组:
const flags = [
'--oneline',
'--decorate',
'--color',
]
await Google 开源 zx,用 async/await 编写 shell 脚本 - 今日头条 git log ${flags}`如果执行的程序返回非零退出代码,则将抛出 ProcessOutput。
try {
await Google 开源 zx,用 async/await 编写 shell 脚本 - 今日头条 exit 1`
} catch (p) {
console.log(`Exit code: ${p.exitCode}`)
console.log(`Error: ${p.stderr}`)
}ProcessOutput
class ProcessOutput {
readonly stdout: string
readonly stderr: string
readonly signal: string
readonly exitCode: number
toString(): string // Combined stdout & stderr.
}
process 的输出会按照原样捕获。通常,程序在末尾打印一个新行 \n ,如果 ProcessOutput 用作其他 $ 进程的参数,zx 将使用 stdout 并 trim 新行。
const date = await Google 开源 zx,用 async/await 编写 shell 脚本 - 今日头条 date`
await Google 开源 zx,用 async/await 编写 shell 脚本 - 今日头条 echo Current date is ${date}.`retry()
重试回调多次,将在第一次成功尝试后返回,或者在指定尝试次数后抛出错误。
const p = await retry(10, () => Google 开源 zx,用 async/await 编写 shell 脚本 - 今日头条 curl https://medv.io`)
// With a specified delay between attempts.
const p = await retry(20, '1s', () => Google 开源 zx,用 async/await 编写 shell 脚本 - 今日头条 curl https://medv.io`)
// With an exponential backoff.
const p = await retry(30, expBackoff(), () => Google 开源 zx,用 async/await 编写 shell 脚本 - 今日头条 curl https://medv.io`)kill()
杀死进程和所有子进程。 默认情况下,发送信号 SIGTERM,但是可以通过参数指定信号。
const p = Google 开源 zx,用 async/await 编写 shell 脚本 - 今日头条 sleep 999`
setTimeout(() => p.kill('SIGINT'), 100)
await pnothrow()
更改 $ 的行为,使其在非零退出代码时不引发异常。
await Google 开源 zx,用 async/await 编写 shell 脚本 - 今日头条 grep something from-file`.nothrow()
// Inside a pipe():
await Google 开源 zx,用 async/await 编写 shell 脚本 - 今日头条 find ./examples -type f -print0`
.pipe(Google 开源 zx,用 async/await 编写 shell 脚本 - 今日头条 xargs -0 grep something`.nothrow())
.pipe(Google 开源 zx,用 async/await 编写 shell 脚本 - 今日头条 wc -l`)如果只需要 exitCode,可以直接使用 exitCode:
if (await Google 开源 zx,用 async/await 编写 shell 脚本 - 今日头条 [[ -d path]]`.exitCode == 0) {
...
}
// Equivalent of:
if ((await Google 开源 zx,用 async/await 编写 shell 脚本 - 今日头条 [[ -d path]]`.nothrow()).exitCode == 0) {
...
}zx 提供的方法特别多,而且还支持 TypeScript、Markdown Scripts 等诸多方式,可以参考文末的资料,这里不再过多展开。
3.本文总结
本文主要和大家介绍 Google 的 zx 包,其提供了封装子进程创建以及这些进程中 stdout 和 stderr 处理的函数,即本质上是 child_process 的包装器,而包装器最重要的就是 $ 函数。 相信通过本文的阅读,大家对 zx 会有一个初步的了解。
因为篇幅有限,文章并没有过多展开,如果有兴趣,可以在我的主页继续阅读,同时文末的参考资料提供了大量优秀文档以供学习。最后,欢迎大家点赞、评论、转发、收藏!
参考资料
https://github.com/google/zx
https://google.github.io/zx/getting-started
https://google.github.io/zx/markdown-scripts
https://javascript.plainenglish.io/use-zx-js-instead-of-shell-c42ce7ce6b62
https://linuxhandbook.com/login-shell/
https://nodesource.com/blog/how-to-run-shell-and-more-using-Nodejs
https://blog.cloudboost.io/node-js-writing-shell-scripts-using-modern-javascript-instead-of-bash-774e0859f965