所以你刚刚在同一句话中读到了DevOps和JavaScript。你是疯了还是好奇心太强了?无论如何,你不会经常把这两者放在一起。在过去的日子里,JavaScript被用来给网页添加一点洒脱的东西。它不可能在DevOps中有自己的位置,对吗?哦,是的,它有。
JavaScript(以及现在的TypeScript)已经渗入到软件工程的每一个毛孔中。首先,它是一个前端的东西。然后,它变成了后端东西(与Node.js)。然后是物联网。而现在,我们终于带着我们的JS行李来到了DevOps的火车站。下了这趟该死的火车,打开你的行李。在你读完这篇文章之后,你将离成为一名JavaScript DevOps工程师更近一步。

介绍zx
首先,让我向你介绍zx。它是一个让你用JavaScript轻松编写脚本的库。要开始使用,你需要把它安装为一个全局包,用
$ npm i -g zx
很好,现在你在本地有了它,让我们写第一个脚本来试试吧。为了在脚本中使用JS,我们需要放一个标题,表明我们将使用zx。因此,打开你喜欢的编辑器,创建一个新的文件test.mjs ,并在第一行写上这个:
#!/usr/bin/env zx
很好,现在让我们在那里添加一些JavaScript:
#!/usr/bin/env zx
console.log("Hello from JS script")
让我们通过在终端运行以下命令使其可执行:
$ chmod +x test.mjs
然后,让我们用以下命令来运行这个脚本:
$ ./test.mjs
你的脚本向你问好了吗?是的,我们得到了它的工作。如果你想让文件变成.js ,你需要使用不同的语法,像这样:
#!/usr/bin/env zx
;(async function () {
console.log("Hello from JS script")
})()
.mjs 很方便,因为你可以使用顶级的await 调用。但是对于.js ,你需要创建一个async 函数并使用await there 。
你还可以用运行脚本:
$ zx test.mjs
$ zx test.js
酷,我们复习了使用zx的基本知识。让我们来看看一些复杂的例子,看看zx真正的闪光点在哪里。我在我的dotfiles里有一个安装脚本来设置我的环境。它主要是处理我每天使用的程序的安装问题。不久前我用Ruby写的,但下面让我们用JavaScript重写它。
zx的真正用途
我们将创建一个脚本,为我们安装一些东西:vim-plug ripgrep zsh和oh-my-zsh 一个终端的主题。 它还将从我的dotfiles repo中复制dotfiles到本地磁盘的适当位置。而且,它将在.zshrc 。"哇,哇,哇,慢点"--你一定在想。对不起,我有点超前了。我们将从安装ripgrep开始,学习如何从zx脚本中运行命令。
运行命令
简而言之,ripgrep是你的文本/regex搜索伙伴。它的速度超快,而且易于使用。如果你还没有使用它的话,就去看看吧。在安装它之前,让我们检查一下ripgrep 是否可用。如果它已经在那里了,我们显然不想安装它。我们可以从我们的脚本which rg 中运行一个命令,它应该让我们知道ripgrep是否在那里。让我们试试吧:
#!/usr/bin/env zx
await $`which rg`
将脚本保存为install.mjs ,然后通过zx install.mjs 来运行它。它失败了吗?很好,这正是我们的目的所在。如果你没有安装rg ,那么which rg 这个命令会导致脚本失败,提前退出。为什么呢?嗯,这就是zx的工作原理。你可以用$\some command "来指定一个命令,它将返回一个承诺。基本上,它将产生一个子进程并在那里运行指定的命令。如果命令失败,而你又没有处理失败的承诺,程序就会中断。很聪明,是吧?
好了,让我们来处理一个我们没有安装rg (ripgrep)的情况。我们将try catch which rg 命令并在catch 块中安装ripgrep 。我是在Mac上,我使用brew 来安装东西。你可以使用你所使用的任何软件包管理器,我不会告诉你该怎么做,咄。让我们看看我们如何以编程方式安装它:
#!/usr/bin/env zx
console.log(chalk.blue("Checking if rg exists..."))
try {
await $`which rg`
console.log(chalk.green("You already have rg, awesome!"))
} catch {
console.log(chalk.red("Nope, installing rg (rigrep)"))
await $`brew install ripgrep`
}
看起来很整洁,是吧?试着把这段代码保存到install.mjs 文件中,然后用zx install.mjs 运行它。你注意到的第一件事将是一个蓝色的文本说--"检查rg是否存在..."。哇,颜色。是的,你可以使用chalk ,开箱即用的zx,并为你的输出文本上色。
如果你没有rg ,你会在终端得到一个红色的文本,后面是负责安装ripgrep 的命令输出。同样,如果你是在Linux上,你可以用apt ,甚至sudo apt 来代替brew 。你最了解你的系统。
如果你有rg可执行,它将以绿色文本打印出 "你已经有rg了,真棒!",我们就可以进入下一步了。
不抛出异常
我们现在就来探讨一下zx 中的nothrow 方法。为了说明我们如何做到这一点,让我们首先尝试实现将.vimrc 从我的dotfiles复制到home目录下的系统.vimrc 。
我们的想法是让install.mjs 把.vimrc 复制到~/.vimrc ,但它应该询问用户是否要覆盖现有的~/.vimrc 。我们可以用cp -i 命令轻松做到这一点,它将询问你是否希望覆盖你所复制的目的地。下面是对-i 标志的解释:
$ man cp
...
-i Cause cp to write a prompt to the standard error output before copying a file that would overwrite an existing file. If the
response from the standard input begins with the character ‘y’ or ‘Y’, the file copy is attempted. (The -i option overrides any
previous -n option.)
...
让我们在我们的install.mjs 中这样做:
#!/usr/bin/env zx
console.log(chalk.blue("Copying .vimrc to ~/.vimrc"))
await $`cp -i .vimrc ~/.vimrc`
保存文件并运行zx install.mjs 。如果你第一次运行它,并且没有文件,你将不会得到覆盖的提示。但是,如果你重新运行它,它问你是否要覆盖--输入n ,表示不,就会停止脚本。为什么?嗯,cp -i .vimrc ~/.vimrc 会像这样返回退出代码1:
$ zx test.mjs
Copying .vimrc to ~/.vimrc
$ cp -i .vimrc ~/.vimrc
overwrite ~/.vimrc? (y/n [n]) n
not overwritten
Error: overwrite ~/.vimrc? (y/n [n]) not overwritten
at file:///Users/.../test.mjs:4:8
exit code: 1
你的第一个想法一定是--让我们做一个try catch 块,抓住那个命令,不让它结束我们的脚本。你是对的,这可以做到。但是,我们想在这里尝试一下nothrow 方法。让我们像这样把我们的命令包在里面:
#!/usr/bin/env zx
console.log(chalk.blue("Copying .vimrc to ~/.vimrc"))
await nothrow($`cp -i .vimrc ~/.vimrc`)
然后,当我们运行zx install.mjs ,我们得到:
$ zx test.mjs
Copying .vimrc to ~/.vimrc
$ cp -i .vimrc ~/.vimrc
overwrite /Users/nikolalsvk/.vimrc? (y/n [n]) n
not overwritten
所以,nothrow 不会使我们的脚本突然结束,而且它将默默地忽略 "失败 "的命令。多么整洁啊!我们可以在这里做别的事情吗?当然可以。准备好迎接奖励回合吧。
奖励回合:获得操作系统的归属地
在前面的例子中,我们经常使用~/ 。我们怎样才能使它变得更加不可知和 "正确 "呢?幸运的是,有os.homedir() ,我们可以使用并确保我们是安全的。对吗?对的。让我们重构一下我们的代码以使用它:
#!/usr/bin/env zx
const homeDir = os.homedir()
console.log(chalk.blue(`Copying .vimrc to ${homeDir}/.vimrc`))
await nothrow($`cp -i .vimrc ${homeDir}/.vimrc`)
哦,哇,但这能行吗?你知道该怎么做,保存文件,然后运行zx install.mjs 。你应该得到与下面类似的东西:
$ zx test.mjs
Copying .vimrc to /Users/nikolalsvk/.vimrc
$ cp -i .vimrc /Users/nikolalsvk/.vimrc
overwrite /Users/nikolalsvk/.vimrc? (y/n [n]) n
not overwritten
现在每个会读到你的JS脚本的人都会--我很幸运能认识/雇用/工作/和这个人在一起,因为你是如此贴心和关心。但玩笑归玩笑,这里有一个极好的事情是,你不需要明确地导入os 来使用它,zx 已经为我们做了,这导致了一个干净而简短的脚本来复制一个文件。谢谢你,zx ,你真棒。
让我们尝试一下zx 中的另一个功能--提问的能力。
提出问题
让我们使用之前复制文件的例子,但让我们写出我们的逻辑,询问用户是否要覆盖该文件。感谢zx ,我们有了可以使用的question 方法。让我们像这样试一下:
#!/usr/bin/env zx
const homeDir = os.homedir()
console.log(chalk.blue(`Copying .vimrc to ${homeDir}/.vimrc`))
if (fs.exists(`${homeDir}/.vimrc`)) {
const overwrite = await question(
`Do you want to overwrite ${homeDir}/.vimrc? (y/n [n]) `
)
if (overwrite.toLowerCase().startsWith("y")) {
console.log(chalk.green(`Overwriting ${homeDir}/.vimrc`))
await $`cp .vimrc ${homeDir}/.vimrc`
} else {
console.log(chalk.blue(`Not overwritting ${homeDir}/.vimrc`))
}
} else {
await $`cp .vimrc ${homeDir}/.vimrc`
}
这里有很多事情要做。我们首先检查${homeDir}/.vimrc 是否存在。如果存在,我们询问用户是否要覆盖它。如果小写字母的答案是 "y",我们就覆盖该文件。如果不是,我们就打印出该脚本不会覆盖该文件。最后,如果没有${homeDir}/.vimrc ,我们就调用基本的cp 命令,而没有之前的内置提示。
如果我们运行脚本并说'y',这就是输出结果:
$ zx test.mjs
Copying .vimrc to /Users/nikolalsvk/.vimrc
Do you want to overwrite /Users/nikolalsvk/.vimrc? (y/n [n]) y
Overwriting /Users/nikolalsvk/.vimrc
$ cp .vimrc /Users/nikolalsvk/.vimrc
而且,如果我们输入其他东西或者直接按回车键,我们得到的结果是这样的:
$ zx test.mjs
Copying .vimrc to /Users/nikolalsvk/.vimrc
Do you want to overwrite /Users/nikolalsvk/.vimrc? (y/n [n])
Not overwritting /Users/nikolalsvk/.vimrc
很好,我们现在已经了解了zx 的几乎所有基本功能,这些功能使用起来非常容易。
其他功能
我们的安装脚本已经涵盖了一些功能。让我们看看还有什么。
你能fetch 我那个东西吗?
你还可以使用fetch ,以获取任何URL。用它来试试吧:
#!/usr/bin/env zx
const response = await fetch("https://api.github.com/octocat")
console.log(await response.text())
运行后,这是我得到的东西:
$ zx fetch.mjs
$ fetch https://api.github.com/octocat
MMM. .MMM
MMMMMMMMMMMMMMMMMMM
MMMMMMMMMMMMMMMMMMM ____________________________
MMMMMMMMMMMMMMMMMMMMM | |
MMMMMMMMMMMMMMMMMMMMMMM | Keep it logically awesome. |
MMMMMMMMMMMMMMMMMMMMMMMM |_ ________________________|
MMMM::- -:::::::- -::MMMM |/
MM~:~ 00~:::::~ 00~:~MM
.. MMMMM::.00:::+:::.00::MMMMM ..
.MM::::: ._. :::::MM.
MMMM;:::::;MMMM
-MM MMMMMMM
^ M+ MMMMMMMMM
MMMMMMM MM MM MM
MM MM MM MM
MM MM MM MM
.~~MM~MM~MM~MM~~.
~~~~MM:~MM~~~MM~:MM~~~~
~~~~~~==~==~~~==~==~~~~~~
~~~~~~==~==~==~==~~~~~~
:~==~==~==~==~~
cd 你的回家之路
你可以使用cd 命令来轻松地在文件系统中移动。让我们打印出你(也许)不知道你的Unix系统上有的《指环王》日历:
#!/usr/bin/env zx
cd("/usr/share/calendar")
await $`cat calendar.lotr`
你至少应该看到这一部分:
01/05 Fellowship enters Moria
01/09 Fellowship reaches Lorien
01/17 Passing of Gandalf
02/07 Fellowship leaves Lorien
02/17 Death of Boromir
02/20 Meriadoc & Pippin meet Treebeard
02/22 Passing of King Elessar
02/24 Ents destroy Isengard
02/26 Aragorn takes the Paths of the Dead
03/05 Frodo & Samwise encounter Shelob
03/08 Deaths of Denethor & Theoden
03/18 Destruction of the Ring
03/29 Flowering of the Mallorn
04/04 Gandalf visits Bilbo
04/17 An unexpected party
04/23 Crowning of King Elessar
05/19 Arwen leaves Lorien to wed King Elessar
06/11 Sauron attacks Osgiliath
06/13 Bilbo returns to Bag End
06/23 Wedding of Elessar & Arwen
07/04 Gandalf imprisoned by Saruman
07/24 The ring comes to Bilbo
07/26 Bilbo rescued from Wargs by Eagles
08/03 Funeral of King Theoden
08/29 Saruman enters the Shire
09/10 Gandalf escapes from Orthanc
09/14 Frodo & Bilbo's birthday
09/15 Black riders enter the Shire
09/18 Frodo and company rescued by Bombadil
09/28 Frodo wounded at Weathertop
10/05 Frodo crosses bridge of Mitheithel
10/16 Boromir reaches Rivendell
10/17 Council of Elrond
10/25 End of War of the Ring
11/16 Bilbo reaches the Lonely Mountain
12/05 Death of Smaug
12/16 Fellowship begins Quest
还有一些zx"的功能,你可以在GitHub上的官方版本中查看。README是相当详细的,可以广泛地帮助你建立任何你想要的东西。
总结
谢谢你读到这里,这对我意义重大。今天我们学到了很多关于zx 。你现在离成为一名JavaScript DevOps工程师又近了一步,恭喜你 🎉。你是否已经感到自豪和富有成效?不错,很高兴我帮了忙。
我们复习了zx 的几个特点:
- 用
$\command`调用命令的能力 - 它将产生一个你需要等待的进程。它也可以抛出一个你需要捕捉的异常,所以要小心。 - 有
nothrow,可以确保该命令不会破坏你的脚本。 - 我们了解了
questions,以及如何使你的脚本具有交互性。 - 有
fetch,可以从网上获取URL(也许还有本地的?) - 你可以用
cd进行导航。
这篇博文的内容就差不多了。加入新闻通讯,因为我打算用TypeScript做所有这些事。是的,每个人都在重写他们的代码库,这是一个热门的东西。另外,如果你喜欢DevOps JS的想法,请告诉我,我会写更多关于它的内容。
你可以在GitHub上我的dotfiles中找到我从Ruby转换为JavaScript的脚本。这里是完整的install.mjs 文件。如果你喜欢,请留下一颗星。如果你不喜欢,请迅速关闭浏览器标签。