在这篇文章中,我们将学习谷歌的zx库提供了什么,以及我们如何使用它来编写Node.js的shell脚本。然后,我们将学习如何通过建立一个命令行工具来使用zx的功能,帮助我们为新的Node.js项目进行引导配置。
编写shell脚本:问题
创建一个shell脚本--一个由Bash或zsh等shell执行的脚本--可以是自动化重复性任务的一个好方法。Node.js似乎是编写shell脚本的理想选择,因为它为我们提供了大量的核心模块,并允许我们导入任何我们选择的库。它还让我们可以使用JavaScript提供的语言功能和内置函数。
但是,如果你试着写一个shell脚本在Node.js下运行,你可能发现它并不像你希望的那样顺利。你需要为子进程编写特殊的处理方法,处理转义的命令行参数,然后最终在stdout (标准输出)和stderr (标准错误)上打转。这不是特别直观,而且会使shell脚本变得相当笨拙。
Bash shell脚本语言是编写shell脚本的一个流行选择。不需要写代码来处理子进程,而且它有内置的语言功能来处理stdout 和stderr 。但是,用Bash编写shell脚本也不是那么容易。语法可能相当混乱,使得它很难实现逻辑,或者处理诸如提示用户输入的事情。
谷歌的zx库有助于使用Node.js编写shell脚本变得高效和愉快。
跟随的要求
跟随本文的学习有几个要求。
- 理想情况下,你应该熟悉JavaScript和Node.js的基础知识。
- 你需要适应在终端中运行命令。
- 你需要安装Node.js>= v14.13.1。
本文中所有的代码都可以在GitHub上找到。
谷歌的zx是如何工作的?
Google's zx提供了一些函数,这些函数包括创建子进程,以及处理stdout 和stderr 这些进程。我们将使用的主要函数是$ 。下面是一个运行中的例子。
import { $ } from "zx";
await $`ls`;
这是执行该代码的输出。
$ ls
bootstrap-tool
hello-world
node_modules
package.json
README.md
typescript
上面的例子中的JavaScript语法可能看起来有点古怪。它使用了一种叫做标签模板字面的语言特性。它在功能上与编写await $("ls") 。
谷歌的zx提供了其他几个实用功能,使shell脚本更容易,例如。
cd().这允许我们改变我们当前的工作目录。question().这是对Node.jsreadline模块的一个封装。它使我们可以直接提示用户输入。
除了zx提供的实用功能外,它还为我们提供了几个流行的库,例如。
- chalk。这个库允许我们在脚本的输出中添加颜色。
- minimist。一个可以解析命令行参数的库。然后它们被暴露在一个
argv对象下。 - fetch。Fetch API的一个流行的Node.js实现。我们可以用它来进行 HTTP 请求。
- fs-extra。一个暴露Node.js核心fs模块的库,以及一些额外的方法,使其更容易与文件系统一起工作。
现在我们知道zx给了我们什么,让我们用它来创建我们的第一个shell脚本。
使用谷歌zx的Hello World
首先,让我们创建一个新的项目。
mkdir zx-shell-scripts
cd zx-shell-scripts
npm init --yes
然后我们可以安装zx 库。
npm install --save-dev zx
注意:zx 文档建议用npm全局安装该库。通过将其安装为我们项目的本地依赖项,我们可以确保zx始终被安装,并控制我们的shell脚本使用的版本。
顶层await
为了在Node.js中使用顶层await ,即async 函数之外的await ,我们需要在ECMAScript(ES)模块中编写代码,它支持顶层await 。package.json我们可以通过在我们的"type": "module" ,表明项目中的所有模块都是ES模块,或者我们可以将单个脚本的文件扩展名设置为.mjs 。在本文的例子中,我们将使用.mjs 文件扩展。
运行一个命令并捕获其输出
让我们创建一个名为hello-world.mjs 的新脚本。我们将添加一个shebang行,它告诉操作系统(OS)内核用node 程序来运行这个脚本。
#! /usr/bin/env node
现在我们将添加一些使用zx来运行命令的代码。
在下面的代码中,我们要运行一个命令来执行ls程序。ls 程序将列出当前工作目录(即脚本所在的目录)中的文件。我们将从命令的进程中捕获标准输出,将其存储在一个变量中,然后将其记录到终端。
// hello-world.mjs
import { $ } from "zx";
const output = (await $`ls`).stdout;
console.log(output);
注意:zx 文档建议将/usr/bin/env zx 放在我们脚本的 shebang 行中,但我们使用/usr/bin/env node 来代替。这是因为我们已经把zx 作为我们项目的本地依赖项来安装。然后我们明确地从zx 包中导入我们想要使用的函数和对象。这有助于明确我们脚本中使用的依赖性来自哪里。
然后,我们将使用chmod来使该脚本可执行。
chmod u+x hello-world.mjs
让我们运行我们的脚本。
./hello-world.mjs
现在我们应该看到下面的输出。
$ ls
hello-world.mjs
node_modules
package.json
package-lock.json
README.md
hello-world.mjs
node_modules
package.json
package-lock.json
README.md
你会注意到我们的shell脚本的输出中的一些东西。
- 我们运行的命令(
ls)被包括在输出中。 - 该命令的输出被显示了两次。
- 在输出的最后有一个额外的新行。
zx 在默认情况下, 模式下运行。它将输出你传递给 函数的命令,同时输出该命令的标准输出。我们可以通过在运行 命令之前加入以下一行代码来改变这种行为。verbose $ ls
$.verbose = false;
大多数命令行程序,如ls ,都会在其输出的末尾输出一个新行字符,以使输出在终端中更易读。这对可读性有好处,但由于我们要将输出存储在一个变量中,我们不希望有这个额外的新行。我们可以用JavaScript的String#trim()函数把它去掉。
- const output = (await $`ls`).stdout;
+ const output = (await $`ls`).stdout.trim();
如果我们再次运行我们的脚本,我们会看到事情看起来好多了。
hello-world.mjs
node_modules
package.json
package-lock.json
在TypeScript中使用谷歌的zx
如果我们想在TypeScript中编写使用zx 的shell脚本,有几个小的差别我们需要说明。
注意:TypeScript编译器提供了一些配置选项,允许我们调整它如何编译我们的TypeScript代码。考虑到这一点,下面的TypeScript配置和代码是为了在大多数TypeScript版本下工作。
首先,让我们安装我们运行TypeScript代码所需的依赖性。
npm install --save-dev typescript ts-node
ts-node包提供了一个TypeScript执行引擎,允许我们转译和运行TypeScript代码。
我们需要创建一个包含以下配置的tsconfig.json 文件。
{
"compilerOptions": {
"target": "es2017",
"module": "commonjs"
}
}
现在让我们创建一个名为hello-world-typescript.ts 的新脚本。首先,我们将添加一个shebang行,告诉我们的操作系统内核使用ts-node 程序来运行脚本。
#! ./node_modules/.bin/ts-node
为了在我们的TypeScript代码中使用await 关键字,我们需要把它包装在一个立即调用的函数表达式(IIFE)中,正如zx文档中建议的那样。
// hello-world-typescript.ts
import { $ } from "zx";
void (async function () {
await $`ls`;
})();
然后我们需要使脚本可执行,这样我们就可以直接执行它。
chmod u+x hello-world-typescript.ts
当我们运行该脚本时。
./hello-world-typescript.ts
......我们应该看到以下输出。
$ ls
hello-world-typescript.ts
node_modules
package.json
package-lock.json
README.md
tsconfig.json
在TypeScript中用zx 编写脚本与使用JavaScript相似,但需要对我们的代码进行一些额外的配置和包装。