编写Shell脚本的问题
创建一个由Bash或者zsh执行的shell脚本,是自动化重复任务的好方法。Node.js似乎是编写shell脚本的理想选择,因为它为我们提供了许多核心模块,并允许我们导入任何我们选择的库。它还允许我们访问JavaScript提供的语言特性和内置函数。
如果你尝试编写运行在Node.js中的shell脚本,你会发现这没有你想象中的那么顺利。你需要为子进程编写特殊的处理程序,注意转义命令行参数,然后最终与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的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.js readline模块的包装器。它使提示用户输入变得简单明了。
除了zx提供的实用功能外,它还为我们提供了几个流行的库,比如:
- chalk。这个库允许我们为脚本的输出添加颜色。
- minimist。一个解析命令行参数的库。然后它们在
argv
对象下被暴露出来。 - fetch。Fetch API的Node.js实现。我们可以用它来进行HTTP请求。
- fs-extra。一个暴露Node.js核心
fs
模块的库,以及一些额外的方法,使其更容易与文件系统一起工作。
现在我们知道了zx给了我们什么,让我们用它创建第一个shell脚本。
zx如何使用
首先,我们先创建一个新项目:
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
,也就是await
位于async
函数的外部,我们需要在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);