莫方教程网

专业程序员编程教程与实战案例分享

Google 开源 zx,用 async/await 编写 shell 脚本

家好,很高兴又见面了,我是"高级前端进阶",由我带着大家一起关注前端前沿、深入前端底层技术,大家一起进步,也欢迎大家关注、点赞、收藏、转发!

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 p

nothrow()

更改 $ 的行为,使其在非零退出代码时不引发异常。

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

控制面板
您好,欢迎到访网站!
  查看权限
网站分类
最新留言

    滇ICP备2024046894号-1