在我的职业生涯中已经写过了上百个Bash
脚本,但我的Bash
依然写得很糟糕,每一次我都不得不去查一些简单逻辑结构的语法。如果我想通过curl
或者sed
来做一些事情,我也必须去查找man
文档。
然后,有一天,我看到六个字母的语言[译者注:这里指NodeJS] — 一门在过去十年里我几乎每一天都在使用的语言,这才让我幡然醒悟。结果是你可以使用JavaScript
来写脚本!
在这篇教程中,我将会在使用Node.js
和npm
创建一个脚本或者命令行工具方面给你一些我的想法。特别地我们将会包括以下内容:
使用npm
封装一个新的shell
命令解析命令行参数从输入流中读取文本和密码发送 snippet输出错误与代码终端输出彩色化渲染ACSII
进度条
我热衷于已经可以工作的例子,所以为了解释这些概念我们将会创建一个新的shell
命令,它的名字为snippet
,可以在我们本地磁盘的文件创建一个Bitbucket Snippet。
这是我们的最终目标成果:
封装 shell 命令
npm
不单单用来管理你的应用和网页的依赖,你还能用它来封装和分发新的shell
命令。
第一步就是通过npm init
[译者注:可以通过npm init -f
直接快速生成一个package.json
]来创建一个新的npm
项目:
这会在我们的项目中创建一个新的package.json
文件,那时我们将需要创建一个JS
文件包含我们的脚本。让我们根据Node.js的传统命名为index.js
。
注意我们必须加一些东西
来告诉我们的shell
如何处理我们的脚本。
接下来我们需要在我们package.json
里面的最顶级增加bin
部分。设置的属性(在我们的例子中是snippet
)将会变成用户在他们的终端处理脚本使用的命令,属性值就是相对于package.json
的脚本位置。
现在我们已经有一个可以工作的shell
命令了!让我们安装它并且测试结果。
真整洁!npm install -g
实际上是将我们脚本链接到path
变量的位置,所以我们能够在任何地方使用它。
在开发环境中我们实际上使用npm link
便利地将我们的index.js
软链接到path
变量的位置。
当我们开发完成的时候,我们可以通过npm publish
将我们的脚本发布到公共npm
仓库,然后任何人都可以下载安装到他们的机器上:
但是让我们先让我们的脚本能够工作先!
解析命令行参数
我们的脚本现在需要一些用户的输入:他们的Bitbucket名字,他们的密码,还有作为snippet
上传的文件位置。典型的方法就是通过命令的参数传输这些值。
你可以通过process.argv
拿到序列化的参数,但有很多npm
包在解析参数还有选项方面提供了很好的抽象给你。我最喜欢的就是commander,来自Ruby gem
同一个名字的灵感。
一个简单的命令安装它:
上面命令将会把最新版的commander
加入package.json
。我们这时可以通过简单声明式的方式定义我们的选项:
上面代码可读性很强。事实上,这是一个保守的说法。相对于那些我们需要通过switch
来控制的像Bash
,这是一个艺术品。至少,我写的Bash
是这样子的。
让我们快速测试:
很棒!commander
还提供一些简单的帮助输出给我们,基于我们上面提供的配置。
所以我们已经拿到了参数了。但是,让用户在空白的地方输入他们的密码作为选项有一点难用。让我们解决它。
从输入流中读取文本和密码
另一种通用的取回用户的内容的脚本方式是从标准输入流中读。这可以通过process.stdin
实现,但是再说一次,已经有很多npm
包提供了非常好的 API 给我们使用。很多都是基于callback
或者promises
,但是我们将使用co-prompt(基于co),因此我们可以利用 ES6 的yield
关键词。这让我们写异步的代码而不需要callbacks
,看起来更加脚本化。
为了组合使用yield
和co-prompt
,我们需要通过一些co
的魔法来包裹我们的代码:
现在快速测试一下。
很棒!唯一的窍门就是ES6的yield
,所以这只能在用户运行在 node 4.0.0+上面。但是我们可以通过加入--harmony
标志让 0.11.2 版本的也可以正常使用。
发送 snippet
Bitbucket拥有一套非常漂亮的API。在这个例子中我将关注传输单一的文件,但我们可以发送整个目录,改变我们的入口配置,加一些代码等,如果我们需要的话。我最喜欢的node HTTP 客户端是superagent,所以让我们把它加入项目中。
现在就让我们将从用户收集到的数据发送给服务器。superagent
其中一个优点就是在它在处理文件上拥有非常好的API。
现在让我们测试一下。
我们的 snippet 已经发送了!\o/
输出错误与代码
到现在为止我们已经处理了一切正确的情况,但是如果我们上传失败或者用户输入错误的信息呢?UNIX-y
的方法来处理错误就是将标准的错误信息输出并且以非0的状态码结束程序,所以我们也这样子做。
这样子就可以处理错误了。
终端输出彩色化
如果你的用户是在使用体面的shell
,这里也有一些包提供给你使用让你方便彩色化的输出。我喜欢chalk,因为它拥有干净可链式的API以及自动检测用户的shell
支持的颜色。这是有益的提示如果你想将你的脚本分享给 windows 用户的话。
chalk
的命令能够彩色输出同时还能方便跟常规字符串串联起来。
让我们旋转一下(这里我使用了截图,以便你能看到极好的颜色)。
渲染ACSII
进度条
snippets
的API实际上支持任何类型的文件(最多10MB),但是当文件比较大或者网速特别慢的时候就需要在命令行界面显示上传文件进度了。命令行解决方案就是优雅的ASCII
进度条。
progress是现在最常用的npm
包用来渲染进度条。
progress
的API非常简单而且可扩展,唯一的问题就是superagent
当前node版本没有事件能够订阅我们上传的进度。
我们可以通过创建一个可读的流并且增加一个事件来触发请求。然后我们初始化进度条为0,当事件触发的时候不断增加。
下面是一个比较快的网速下上传大约6MB的文件的截图:
很棒!用户现在就能够看到他们上传的进度并且知道什么时候上传完成。
总结
我们只不过接触了用Node开发命令行脚本的冰山一角。在每一期的Atwood’s Law都有很多npm
包优雅地处理标准输入、管理并行任务、监听文件、管道流、压缩、ssh、git、还有任何你能用Bash
做到的。更多地,还有非常好的API来处理子进程如果你需要其他shell脚本处理(当JavaScript处理不了的时候)。
我们上面例子的源码是在available on Bitbucket的license下,并且已经发布到npm仓库。我这里也提一些上面没有讲到的概念,比如OAuth
,这样子你就不需要每次都输入用户名跟密码。如果你想自己简单体验一下:
出处:凹凸实验室( https://aotu.io/notes//12/23/building-command-line-tools-with-node-js/)