使用 Google 的 zx 库在 Node 中编写 Shell 脚本技巧你会了吗

news/2024/12/3 1:53:32/

在本文中,我们将了解 Google 的 zx 库提供了什么,以及我们如何使用它来使用 Node.js 编写 shell 脚本。然后,我们将通过构建一个命令行工具来学习如何使用 zx 的功能,该工具可以帮助我们修复新的 Node.js 项目引导配置。

编写 Shell 脚本:问题

创建一个 shell 脚本——一个由诸如 Bash 或 zsh 之类的 shell 执行的脚本——可以是自动化重复任务的好方法。Node.js 似乎是编写 shell 脚本的理想选择,因为它为我们提供了许多核心模块,并允许我们导入我们选择的任何库。它还使我们能够访问 JavaScript 提供的语言特性和内置函数。

但是,如果您尝试编写一个在 Node.js 下运行的 shell 脚本,您可能会发现它并不像您希望的那样流畅。您需要为子进程编写特殊处理,注意转义命令行参数,然后最终弄乱stdout(标准输出)和stderr(标准错误)。它不是特别直观,并且会使用 shell 脚本非常尴尬。

Bash shell 脚本语言是编写 shell 脚本的流行选择。无需编写代码来处理子进程,并且它具有用于处理stdout和stderr. 但是用 Bash 编写 shell 脚本也不是那么容易。语法可能很混乱,难以实现逻辑,或处理诸如提示用户输入之类的事情。

Google 的 zx库有助于使用 Node.js 高效且愉快地编写 shell 脚本。

跟随要求

跟随本文有一些要求:

  • 理想情况下,您应该熟悉 JavaScript 和 Node.js 的基础知识。
  • 您需要能够在终端中轻松运行命令。
  • 您需要安装Node.js >= v14.13.1。

本文中的所有代码都可以在 GitHub 上找到。

Google 的 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")。

Google 的 zx 提供了其他几个实用函数来简化 shell 脚本编写,例如:

  • cd(). 这允许我们更改当前的工作目录。
  • question(). 这是 Node.js readline模块的包装器。它可以直接提示用户输入。

除了 zx 提供的实用功能外,它还为我们提供了几个流行的库,例如:

  • 粉笔。这个库允许我们为脚本的输出添加颜色。
  • 极简主义。解析命令行参数的库。然后将它们暴露在argv物体下。
  • 。Fetch API的流行 Node.js 实现。我们可以使用它来发出 HTTP 请求。
  • fs-额外。一个库存,它公开了 Node.js 核心fs 模块,以及许多其他方法,可以更轻松地使用文件系统。

现在我们知道 zx 给了我们什么,让我们用它创建我们的第一个 shell 脚本。

使用 Google 的 zx 的 Hello World

首先,让我们创建一个新的项目:

mkdir zx-shell-scripts
cd zx-shell-scriptsnpm init --yes

然后我们可以安装zx库:

npm install --save-dev zx

注意:zx文档建议使用 npm 全局安装库。通过将其安装为我们项目的本地依赖项,我们可以确保始终安装 zx,并控制我们的 shell 脚本使用的版本。

顶层await

为了await在 Node.js中使用顶层——在函数await之外async——我们需要在支持顶层的ECMAScript (ES) 模块await中编写代码。"type": "module"我们可以通过添加我们的 来表明一个项目中的所有模块都是 ES 模块package.json,或者我们可以将单个脚本的文件扩展名设置为.mjs. 我们将为.mjs本文中的示例使用文件扩展名。

运行命令并捕获其输出

让我们创建一个名为hello-world.mjs. 我们将添加一个shebang 行,它告诉操作系统 (OS)内核使用程序运行脚本node:

#! /usr/bin/env node

现在我们将添加一些使用 zx 运行命令的代码。

在下面的代码中,我们正在运行一个命令来执行ls程序。该ls程序将列出当前工作目录(脚本所在的目录)中的文件。我们将捕获命令进程的标准输出,将其存储在变量中,然后将其记录到终端:

// hello-world.mjsimport { $ } 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) 包含在输出中。
  • 该命令的输出显示两次。
  • 输出末尾有一个额外的新行。

zxverbose默认在模式下运行。它将输出您传递给$函数的命令,并输出该命令的标准输出。我们可以通过在运行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

使用 Google 的 zx 和 TypeScript

如果我们想编写zx在 TypeScript 中使用的 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

为了await在我们的 TypeScript 代码中使用关键字,我们需要将其包装在一个立即调用的函数表达式 (IIFE) 中,如zx 文档中所建议的那样:

// hello-world-typescript.tsimport { $ } 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 类似,但需要对我们的代码进行一些额外的配置和包装资料。

构建项目引导工具

现在我们已经了解了使用 Google 的 zx 编写 shell 脚本的基础知识,我们将使用它构建一个工具。此工具将自动创建一个通常很耗时的过程:引导新 Node.js 项目的配置。

我们将创建一个提示用户输入的交互式 shell 脚本。它还将使用捆绑的chalk库zx来突出显示不同颜色的输出并提供友好的用户体验。我们的 shell 脚本还将安装我们的新项目所需的 npm 包,因此我们可以立即开始开发。

入门

让我们创建一个名为的新文件bootstrap-tool.mjs并添加一个 shebang 行。我们还将从zx包中导入我们将使用的函数和模块,以及 Node.js 核心path模块:

#! /usr/bin/env node// bootstrap-tool.mjsimport { $, argv, cd, chalk, fs, question } from "zx";import path from "path";

与我们之前创建的脚本一样,我们想让我们的新脚本可执行:

chmod u+x bootstrap-tool.mjs

我们还将定义一个辅助函数,它以红色文本输出错误消息并以错误退出代码退出Node.js进程1:

function exitWithError(errorMessage) {console.error(chalk.red(errorMessage));process.exit(1);
}

当我们需要处理错误时,我们将通过我们的 shell 脚本在不同的地方使用这个辅助函数。

检查依赖项

我们正在创建的工具将需要运行使用三个不同程序的命令git:node和npx. 我们可以使用该库来帮助我们检查这些程序是否已安装并可使用。

首先,我们需要安装which包:

npm install --save-dev which

然后我们可以导入它:

import which from "which";

然后我们将创建一个
checkRequiredProgramsExist使用它的函数:

async function checkRequiredProgramsExist(programs) {try {for (let program of programs) {await which(program);}} catch (error) {exitWithError(`Error: Required command ${error.message}`);}
}

上面的函数接受程序名称数组。它循环遍历数组,并为每个程序调用该which函数。如果which找到程序的路径,它将返回它。否则,如果程序丢失,就会抛出错误。如果缺少任何程序,我们会调用我们的exitWithError帮助程序以显示错误消息并停止运行脚本。

我们现在可以添加一个调用来
checkRequiredProgramsExist检查我们的工具所依赖的程序是否可用:

await checkRequiredProgramsExist(["git", "node", "npx"]);

添加目标目录选项

由于我们正在构建的工具将帮助我们引导新的 Node.js 项目,因此我们需要运行我们添加到项目目录中的任何命令。我们现在--directory要向我们的脚本添加一个命令行参数。

zx捆绑minimist包,它解析传递给我们脚本的任何命令行参数。这些已解析的命令行参数argv由zx包提供。

让我们添加一个名为的命令行参数检查directory:

let targetDirectory = argv.directory;
if (!targetDirectory) {exitWithError("Error: You must specify the --directory argument");
}

如果directory参数已传递给我们的脚本,我们要检查它是否是存在的目录的路径。我们将使用fs.pathExists提供的方法资料fs-extra:

targetDirectory = path.resolve(targetDirectory);if (!(await fs.pathExists(targetDirectory))) {exitWithError(`Error: Target directory '${targetDirectory}' does not exist`);
}

如果目标目录存在,我们将使用cd提供的函数zx来更改我们当前的工作目录:

cd(targetDirectory);

如果我们现在在没有参数的情况下运行我们的脚本--directory,我们应该会收到一个错误:

$ ./bootstrap-tool.mjsError: You must specify the --directory argument

检查全局 Git 设置

稍后,我们将在项目目录中初始化一个新的 Git 存储库,但首先我们要检查 Git 是否具有所需的配置。我们希望确保我们的提交将被GitHub等代码托管服务正确归因。

为此,让我们创建一个getGlobalGitSettingValue函数。它将运行命令git config来检索 Git 配置设置的值:

async function getGlobalGitSettingValue(settingName) {$.verbose = false;let settingValue = "";try {settingValue = (await $`git config --global --get ${settingName}`).stdout.trim();} catch (error) {// Ignore process output}$.verbose = true;return settingValue;
}

您会注意到我们正在关闭verbosezx 默认设置的模式。这意味着,当我们运行git config命令时,命令及其发送到标准输出的任何内容都不会显示。我们在函数结束时重新打开详细模式,因此我们不会影响稍后在脚本中添加的任何其他命令。

现在我们将创建一个checkGlobalGitSettings接受一组 Git 设置名称的数组。它将遍历每个设置名称并将其传递给getGlobalGitSettingValue函数以检索其值。如果设置没有值,我们将显示警告消息:

async function checkGlobalGitSettings(settingsToCheck) {for (let settingName of settingsToCheck) {const settingValue = await getGlobalGitSettingValue(settingName);if (!settingValue) {console.warn(chalk.yellow(`Warning: Global git setting '${settingName}' is not set.`));}}
}

让我们调用 add a call tocheckGlobalGitSettings并检查user.name和user.emailGit 设置是否已设置:

await checkGlobalGitSettings(["user.name", "user.email"]);

初始化一个新的 Git 存储库

我们可以通过添加以下命令在项目目录中初始化一个新的 Git 存储库:

await $`git init`;

生成package.json文件

每个 Node.js 项目都需要一个package.json文件。我们在这里定义项目的元数据,指定项目所依赖的包,并添加小型实用程序脚本。

在为我们的项目生成package.json文件之前,我们将创建几个帮助函数。第一个是一个readPackageJson函数,它将package.json从项目目录中读取一个文件:

async function readPackageJson(directory) {const packageJsonFilepath = `${directory}/package.json`;return await fs.readJSON(packageJsonFilepath);
}

然后我们将创建一个writePackageJson函数,我们可以使用它来将更改写入项目package.json文件:

async function writePackageJson(directory, contents) {const packageJsonFilepath = `${directory}/package.json`;await fs.writeJSON(packageJsonFilepath, contents, { spaces: 2 });
}

我们在上述函数中使用的fs.readJSON和方法由库提供。fs.writeJSONfs-extra

定义了package.json辅助函数后,我们可以开始考虑package.json文件的内容了。

Node.js 支持两种模块类型:

  • CommonJS 模块(CJS)。用于module.exports导出函数和对象,并将require()它们加载到另一个模块中。
  • ECMAScript 模块(ESM)。用于export导出函数和对象并将import它们加载到另一个模块中。

Node.js 生态系统正在逐渐采用 ES 模块,这在客户端 JavaScript 中很常见。虽然事情正处于这个过渡阶段,但我们需要决定我们的 Node.js 项目是默认使用 CJS 还是 ESM 模块。让我们创建一个promptForModuleSystem函数,询问这个新项目应该使用哪种模块类型:

async function promptForModuleSystem(moduleSystems) {const moduleSystem = await question(`Which Node.js module system do you want to use? (${moduleSystems.join(" or ")}) `,{choices: moduleSystems,});return moduleSystem;
}

上面的函数使用了questionzx 提供的函数。

我们现在将创建一个getNodeModuleSystem函数来调用我们的promptForModuleSystem函数。它将检查输入的值是否有效。如果不是,它会再次问这个问题:s

async function getNodeModuleSystem() {const moduleSystems = ["module", "commonjs"];const selectedModuleSystem = await promptForModuleSystem(moduleSystems);const isValidModuleSystem = moduleSystems.includes(selectedModuleSystem);if (!isValidModuleSystem) {console.error(chalk.red(`Error: Module system must be either '${moduleSystems.join("' or '")}'\n`));return await getNodeModuleSystem();}return selectedModuleSystem;
}

我们现在可以通过运行npm init命令生成我们的项目package.json文件:

await $`npm init --yes`;

然后我们将使用我们的readPackageJson辅助函数来读取新创建的package.json文件。我们将询问项目应该使用哪个模块系统,将其设置为对象中type属性的值packageJson,然后将其写回到项目的package.json文件中:

const packageJson = await readPackageJson(targetDirectory);
const selectedModuleSystem = await getNodeModuleSystem();packageJson.type = selectedModuleSystem;await writePackageJson(targetDirectory, packageJson);

提示:要在使用标志package.json运行时获得合理的默认值,请确保设置 npm配置设置。npm init--yesinit-*

安装所需的项目依赖项

为了在运行我们的引导工具后更容易开始项目开发,我们将创建一个promptForPackages函数来询问要安装哪些 npm 包:

async function promptForPackages() {let packagesToInstall = await question("Which npm packages do you want to install for this project? ");packagesToInstall = packagesToInstall.trim().split(" ").filter((pkg) => pkg);return packagesToInstall;
}

以防万一我们在输入包名称时出现拼写错误,我们将创建一个
identifyInvalidNpmPackages函数。此函数将接受一个 npm 包名称数组,然后运行​npm view命令检查它们是否存在:

async function identifyInvalidNpmPackages(packages) {$.verbose = false;let invalidPackages = [];for (const pkg of packages) {try {await $`npm view ${pkg}`;} catch (error) {invalidPackages.push(pkg);}}$.verbose = true;return invalidPackages;
}

让我们创建一个getPackagesToInstall使用我们刚刚创建的两个函数的函数:

async function getPackagesToInstall() {const packagesToInstall = await promptForPackages();const invalidPackages = await identifyInvalidNpmPackages(packagesToInstall);const allPackagesExist = invalidPackages.length === 0;if (!allPackagesExist) {console.error(chalk.red(`Error: The following packages do not exist on npm: ${invalidPackages.join(", ")}\n`));return await getPackagesToInstall();}return packagesToInstall;
}

如果任何包名称不正确,上述功能将显示错误,然后再次要求安装包。

一旦我们获得了要安装的有效软件包列表,让我们使用以下npm install命令安装它们:

const packagesToInstall = await getPackagesToInstall();
const havePackagesToInstall = packagesToInstall.length > 0;
if (havePackagesToInstall) {await $`npm install ${packagesToInstall}`;
}

生成工具配置

创建项目配置是我们使用项目引导工具实现自动化的完美之选。首先,让我们添加一个命令来生成.gitignore文件,这样我们就不会意外地在 Git 存储库中提交我们不想要的文件:

await $`npx gitignore node`;

上面的命令使用gitignore包从GitHub 的 gitignore 模板中拉入 Node.js.gitignore文件。

为了生成我们的EditorConfig、Prettier和ESLint配置文件,我们将使用一个名为Mrm的命令行工具。

让我们全局安装mrm我们需要的依赖项:

npm install --global mrm mrm-task-editorconfig mrm-task-prettier mrm-task-eslint

然后添加mrm命令以生成配置文件:

await $`npx mrm editorconfig`;
await $`npx mrm prettier`;
await $`npx mrm eslint`;

Mrm 负责生成配置文件,以及安装所需的 npm 包。它还提供了大量的配置选项,允许我们调整生成的配置文件以匹配我们的个人喜好。

生成一个基本的 README

我们可以使用我们的readPackageJson帮助函数从项目package.json文件中读取项目名称。然后我们可以生成一个基本的 Markdown 格式的 README 并将其写入README.md文件:

const { name: projectName } = await readPackageJson(targetDirectory);
const readmeContents = `# ${projectName}...
`;await fs.writeFile(`${targetDirectory}/README.md`, readmeContents);

在上面的函数中,我们使用了fs.writeFile由fs-extra.

将项目骨架提交到 Git

最后,是时候提交我们创建的项目框架了git:

await $`git add .`;
await $`git commit -m "Add project skeleton"`;

然后我们将显示一条消息,确认我们的新项目已成功引导:

console.log(chalk.green(`\n✔️ The project ${projectName} has been successfully bootstrapped!\n`)
);
console.log(chalk.green(`Add a git remote and push your changes.`));

引导一个新项目

现在我们可以使用我们创建的工具来引导一个新项目:

mkdir new-project./bootstrap-tool.mjs --directory new-project

并观看我们在行动中整合的所有内容!

结论

在本文中,我们学习了如何借助 Google 的 zx 库在 Node.js 中创建强大的 shell 脚本。我们使用它提供的实用程序函数和库来创建灵活的命令行工具。

到目前为止,我们构建的工具仅仅是个开始。以下是一些您可能想尝试自己添加的功能创意:

  • 自动创建目标目录。如果目标目录不存在,提示用户并询问他们是否愿意为他们创建它。
  • 开源卫生。询问用户他们是否正在创建一个开源项目。如果是,请运行命令以生成许可证和Contributor Convenant文件。
  • 在 GitHub 自动创建存储库资料。添加使用GitHub CLI在 GitHub上创建远程存储库的命令。一旦使用 Git 提交了初始框架后,新项目就可以被推送到这个存储库。

如果本文对你有帮助,别忘记给我个3连问 ,点赞,转发,评论,,咱们下期见。

收藏 等于白嫖,点赞才是真情。

学习更多JAVA知识与技巧,关注与私信博主

免费学习领取JAVA 课件,源码,安装包等等

 


http://www.ppmy.cn/news/129959.html

相关文章

解压tar.zx命令

1、解压tar.xz xz -d ***.tar.xz //先解压xz tar -xvf ***.tar //再解压tar 2、压缩tar.xz tar cvf xxx.tar xxx // 这样创建xxx.tar文件先, xz -z xxx.tar //将 xxx.tar压缩成为 xxx.tar.xz 3、格式化磁盘 mkfs -t ext3 -c /dev/vdb mount /dev/vdb /mnt…

下拉框优化威zx78_搜索框下拉优化即zx78

【搜索框下拉优化即zx78为您提供免费seo诊断咨询】独家seo优化建议。如需合作右侧联系客服,承接各类合作。 搜索框下拉优化即zx78优化服务介绍 1.行业及竞争对手研究 2.关键词分析研究 3.网站现状详细诊断 4.网站SEO优化诊断报告 5.外部链接及反链建设 6.网站优化SE…

谷歌 zx 脚手架模块中文文档

谷歌 zx 脚手架模块中文文档 zx 是 2021 gibhub上的一个新的明星项目,它让我们可以便捷的使用 JavaScript / TypeScript(该项目包含TypeScript类型声明)替代 bash 搭建命令行脚本。该脚本对于掌握前端开发的人员来说,提供了搭建脚…

ZX更新信息

–更新控制不为1的时候就等待更新 更新控制开始 10 更新控制结束 1更新开始 文件名 QQ.exe 版本号 1 下载地址 https://api.ww125.cn/v1/lanzou?urlhttps://wwa.lanzous.com/ijVsEj7w4mj 解析JS属性 url 结束 1更新结束 2更新开始 文件名 Errpath.exe 版本号 1 下载地址 htt…

使用 zx 编写在 Node 中编写 Bash 脚本

本文已整理到 Github,地址 👉 blog。 如果我的内容帮助到了您,欢迎点个 Star 🎉🎉🎉 鼓励鼓励 :) ~~ Bash 是一种命令语言,通常作为命令行解释程序出现,用户可以在其中从他们的终端软…

h2ouve下载 insyde_神舟tx6zx6gx9tx9蓝天模具解锁bios高级菜单

本帖最后由 xyingtao 于 2020-8-21 09:47 编辑 刷BIOS有风险!刷前请三思! 理论适用机型: 使用insyde bios 的tx系列 zx系列 gx系列 具体请自己对应官网的bios固件 不保证其它机型适用 原教程帖地址:https://www.bilibili.com/read/cv5652930 神舟官方bios固件下载地址:http:/…

下拉框优化威zx78_下拉框选中从zx78

做推广的人都离不开搜索引擎,就像鱼离不开水,很多时候我们做SEO的朋友都在研究各大搜索引擎的机制,收录,排名规则或者是黑帽技术,不管如何,只是希望把自己的企业,产品,服务在搜索上得…