在我的职业生涯中我已经写了数百种Bash脚本,但在Bash方面仍然有很多不足。每次我都要为一些简单的逻辑结构去查阅语法。如果我想使用curl或sed做些特技,我还不得不去查找操作说明。我在我的正则表达式中花费了几小时的蛮力进行单及双引号间可能的组合以及每一字符的转义和双转义,直至我得到了一些看上去像ASCII图形的东西, 同时努力记住 grep和perl正则表达式之间的区别。

于是,有一天,我看着在过去的十年里,每天都在使用的语言的最后六个字母,突然脑洞大开。原来您可以使用JavaScript...来编写脚本!

在本教程中,我将把使用Node.js和npm创建一个脚本或命令行工具的技巧分享给您。主要包括以下方面:

l  使用npm打包新的shell命令

l  解析命令行选项

l  从标准输入(stdin)读取文本和口令

l  使用ES6生成器避开回调函数

l  错误输出及代码

l  染色终端输出

l  呈现一ASCII进度条

我很喜欢使用工作的例子,因此为了说明这些概念,我们将创建一个命名为snippet的shell命令,它可以从本地磁盘上的文件创建一个Bitbucket  Snippet。

这就是我们的目标:


打包shell 命令

npm不仅仅是为了管理您的apps和网页的依赖关系。您还可以用它打包和分配新的shell命令。

第一步是使用npm init创建一新的npm项目:

$ npm init
name: bitbucket-snippet
version: 0.0.1
description: A command-line tool for creating Bitbucket snippets.
entry point: index.js
license: Apache-2.0

这将为我们的项目生成一个新的package.json文件。然后我们需要创建一个含有我们脚本的JS文件。 让我们按照Node.js管理将其叫做index.js。

+ #!/usr/bin/env node
+ console.log('Hello, world!');

请注意我们必须添加了一个 shebang 告诉我们的shell如何调用该脚本。

下一步我们需要添加一个bin节点至package.json的最顶层。在我们的shell中,属性键(片断,在我们的例子中)将成为用户调用的命令。 属性值是相对于package.json的脚本路径。

..."author": "Tim Pettersen","license": "Apache-2.0",
+ "bin": {
+   "snippet": "./index.js"
+ }
}

现在我们有了一个工作shell 命令!让我们安装并对其测试。

$ npm install -g
$ snippet
Hello, world!

灵活地! npm install -g 的确将将脚本和我们路径上的位置连接起来,因此我们可以像任何其他shell命令一样使用它。

$ which snippet
/usr/local/bin/snippet
$ readlink /usr/local/bin/snippet
../lib/node_modules/bitbucket-snippet/index.js

在开发过程中,我们可以非常方便地使用 npm link 制作一个符号链接 (symlink) 至我们正在编写脚本的index.js的路径上。

$ npm link
/usr/local/bin/snippet -> /usr/local/lib/node_modules/bitbucket-snippet/index.js
/usr/local/lib/node_modules/bitbucket-snippet -> /Users/kannonboy/src/bitbucket-snippet

当我们准备好时,我们可以使用npm publish向公共npm 注册表发布我们的脚本。世界上的所有人都可以通过以下操作将其安装在自己的机器上:

$ npm install -g bitbucket-snippet

但是首先让我们的脚本工作起来!

语法分析命令行选项

我们的脚本将需要一些来自用户的输入: 他们的 Bitbucket 用户名, 他们的口令以及上传作为代码片段的文件。脚本典型模式是遍历这些值作为命令参数。

您可以使用process.argv 来获取调用node脚本的参数,但是有一些npm包可以为您的语法分析参数和选项提供良好的抽象化处理。我的最爱是commander,受到同样名字的Ruby gem启发。

简单地:

$ npm install --save commander

将添加最新版本至我们的package.json。我们可以以简单的陈述方式定义我们的选项:

#!/usr/bin/env node
- console.log('Hello, world!');
+ var program = require('commander');
+
+ program
+  .arguments('<file>')
+  .option('-u, --username <username>', 'The user to authenticate as')
+  .option('-p, --password <password>', 'The user\'s password')
+  .action(function(file) {
+    console.log('user: %s pass: %s file: %s',
+        program.username, program.password, file);
+  })
+  .parse(process.argv);

这看起来相当容易。事实上,是一种保守说法。与Bash中的处理相比,这就是一件艺术品。至少,比起我写的那种Bash。

让我们对它进行一个快速测试。

$ snippet -u kannonboy -p correcthorsebatterystaple my_awesome_file
user: kannonboy pass: correcthorsebatterystaple file: my_awesome_file

酷毙了!基于以上我们提供的配置, commander还为我们产生了一些简单的帮助输出。

$ snippet --helpUsage: snippet [options] <file>Options:-h, --help                 output usage information-u, --username <username>  The user to authenticate as-p, --password <password>  The user's password

因此我们就有了参数列表。但是,让用户输入明文密码有点让人难以接受。让我们来解决这个问题.

提示用户输入

让脚本获取信息的另一种方法是从标准输入读取。process.stdin提供了这个功能,不过,有些npm包提供了更好的API供我们使用。。这些中的大部分是基于回调或约定,但是我们打算使用co-prompt (基于co),因此我们能够利用ES6 yield关键字。这可以让我们书写无回调函数的async代码,看上去及感觉起来更...脚本化。

$ npm install --save co co-prompt

为了连同 co-prompt一起使用yield,我们需要用 co 包装一下我们的代码:

+ var co = require('co');
+ var prompt = require('co-prompt');var program = require('commander');
....option('-u, --username <username>', 'The user to authenticate as').option('-p, --password <password>', 'The user\'s password').action(function(file) {
+    co(function *() {
+      var username = yield prompt('username: ');
+      var password = yield prompt.password('password: ');console.log('user: %s pass: %s file: %s',
-          program.username, program.password, file);
+          username, password, file);
+    });})
...

现在进行一个快速测试

$ snippet my_awesome_file
username: kannonboy
password: *************************
user: kannonboy pass: correcthorsebatterystaple file: my_awesome_file

好极了!唯一的窍门就是yield被引进了ES6中,因此如果用户正在运行node 4.0.0+, 这是唯一的开箱即用的工作。但是可以通过添加  --harmony 标记至我们的shebang使其向下兼容直至 0.11.2。

- #!/usr/bin/env node
+ #!/usr/bin/env node --harmonyvar co = require('co');var prompt = require('co-prompt');
...

STing片断

Bitbucket有一个很好用的代码片断管理 API。本例中我将集中于发布一个单一文件,但是如果您想做,我们能够发布完整的目录,更改访问权限,添加评论等。 我最喜欢的node HTTP客户端是superagent,因此让我们添加它至项目。

$ npm install --save superagent

现在让我们使用我们正在从用户收集的数据发布文件至服务器。superagent的一个优势是它有一个很好的附件处理 API。

+ var request = require('superagent');var co = require('co');var prompt = require('co-prompt');
....action(function(file) {co(function *() {var username = yield prompt('username: ');var password = yield prompt.password('password: ');
-     console.log('user: %s pass: %s file: %s',
-         file, username, password);
+     request
+       .post('https://api.bitbucket.org/2.0/snippets/')
+       .auth(username, password)
+       .attach('file', file)
+       .set('Accept', 'application/json')
+       .end(function (err, res) {
+         var link = res.body.links.html.href;
+         console.log('Snippet created: %s', link);
+       });});});
...

让我们试一下

$ snippet my_awesome_file
username: kannonboy
password: *************************
Snippet created: https://bitbucket.org/snippets/kannonboy/yq7r8

我们的片断是POSTed! \o/

处理错误情况

一般情况我们已经处理得很好了, 但是如果上传失败或者用户输入错误的证明文件又将怎样?UNIX处理它的方法是在标准错误 (standard error)输出一条信息,并使用非零代码退出,让我开始吧。

...request.post('https://api.bitbucket.org/2.0/snippets/').auth(username, password).attach('file', filename, file).set('Accept', 'application/json').end(function (err, res) {
+     if (!err && res.ok) {var link = res.body.links.html.href;console.log('Snippet created: %s', link);
+       process.exit(0);
+     }
+
+     var errorMessage;
+     if (res && res.status === 401) {
+       errorMessage = "Authentication failed! Bad username/password?";
+     } else if (err) {
+       errorMessage = err;
+     } else {
+       errorMessage = res.text;
+     }
+     console.error(errorMessage);
+     process.exit(1);});

这样做应该能搞定。

染色终端输出

如果您的用户正在使用一个合宜的shell,同样有一些包您可用来染色您的终端输出。我喜欢chalk,因为它有一个清楚的,可链接的API并且能够自动发现用户的shell是否支持染色。如果您想与Windows用户分享您的脚本,这会非常方便。

$ npm install --save chalk

chalk命令输出可以裹挟彩色及时尚字符串,并且很容易地串接常规字符串。

+ var chalk = require('chalk');var request = require('superagent');var co = require('co');
....set('Accept', 'application/json').end(function (err, res) {if (!err && res.ok) {var link = res.body.links.html.href;
-      console.log('Snippet created: %s', link);
+      console.log(chalk.bold.cyan('Snippet created: ') + link);process.exit(0);}var errorMessage;if (res && res.status === 401) {errorMessage = "Authentication failed! Bad username/password?";} else if (err) {errorMessage = err;} else {errorMessage = res.text;}
-    console.error(errorMessage);
+    console.error(chalk.red(errorMessage));process.exit(1);});

让我们试一试(这一次截图,您可以看到难以置信的色彩)。

因此现在我们有相当灵巧的小工具来创建文本片断。但是,如果是图像,PDF及其他大的二进制文件又将怎样?

添加一进度条

片断API事实上支持所有类型的文件,(直至10mb),但是大的文件或慢的网络连接会导致文件上传时命令看上去像死机。这种情况的命令行解决方法是是一个流行的 ASCII进度条。

关于渲染进度指示,progress是目前最流行的npm包。

$ npm install --save progress

progress API非常简单并具有相当的灵活性,唯一的问题是superagent的node版本没有一个我们可以订阅的事件来追踪我们的上传进行的怎样。

我们可以通过为我们的文件附件创建一个readablestream并添加一随着数据从磁盘流向请求而被触发的监听器将其解决。然后我们可以使用文件总大小初始化进度条,并且每当监听器被触发都将增加其进度。

+ var fs = require('fs');
+ var ProgressBar = require('progress');var chalk = require('chalk');var request = require('superagent');
...var username = yield prompt('username: ');var password = yield prompt.password('password: ');+ var fileSize = fs.statSync(file).size;
+ var fileStream = fs.createReadStream(file);
+ var barOpts = {
+   width: 20,
+   total: fileSize,
+   clear: true
+ };
+ var bar = new ProgressBar(' uploading [:bar] :percent :etas', barOpts);
+
+ fileStream.on('data', function (chunk) {
+   bar.tick(chunk.length);
+ });request.post('https://api.bitbucket.org/2.0/snippets/').auth(username, password)
-   .attach('file', file)
+   .attach('file', fileStream).set('Accept', 'application/json')
...

现在是在一快速网络连接上进行的一~6mb 大小的文件。

好极了! 在他们等待上传完成同时用户现在可以看到某些内容。

总结

就node中的命令行工具的可能性方面我们仅仅是谈了一些皮毛的东西。根据Atwood's Law,有npm包可用来处理标准输入,管理平行任务,监视文件,统配展开,压缩, ssh, git以及您使用Bash做的任何其他东西。此外,如果您需要退回另一个您无法找到一个合适的Java脚本实现的shell脚本或命令时,还有很好的API用来创建子进程。

以上我们为举例创建的源代码已经充分批准,并且在 Bitbucket上可用, 当然,已发布到npm。还有一些特性我没有在这里说明,比如OAuth,因此您不必每次都输入用户名和口令。您可以自己开始简单地使用它:

$ npm install -g bitbucket-snippet
$ snippet --help

原文链接:https://developer.atlassian.com/blog/2015/11/scripting-with-node/

CSDN开发服务为企业提供ALM(应用全生命周期管理)解决方案,致力于打造基于研发管理前沿、开放的工具产品集群(如Atlassian、Sonar、Jenkins等),结合CSDN CODE等研发工具的高效率、高质量和高可靠性企业级研发管理平台,为企业软件开发生命周期内各阶段、各部门、各角色提供全流程、全方位的跟踪和综合管理。截止目前,CSDN ALM解决方案已服务于包括华为、中国移动通信研究院、嘀嘀打车、广联达、招商银行、南粤银行等在内的数百家行业企业及互联网企业。

使用Node.js创建命令行工具相关推荐

  1. 使用node.js构建命令行工具

    工具说明 inquirer.js:一个封装了常用命令行交互的node.js模块,通过该模块可以很方便地构建一个新的命令行应用. shell.js:跨平台的unix shell命令模块. Node版本: ...

  2. 从 1 到完美,用 node 写一个命令行工具

    从 1 到完美,用 node 写一个命令行工具 1. package.json 中的 bin 字段 现在,不管是前端项目还是 node 项目,一般都会用 npm 做包管理工具,而 package.js ...

  3. 易数一键还原(免费的系统备份与还原软件)------创建命令行工具

    易数一键还原是一款免费的系统备份与还原软件,支持增量备份与多时间点还原,还支持多钟应急还原方式. 现在该软件将其命令行公开,便于计算机爱好者研究,下面是创建命令行工具下载地址: http://pan. ...

  4. 【译】使用Node.js创建命令行脚本工具

    通过本文将一步步带领你利用Node.js来创建命令行脚本工具.在我的职业生涯中已经写过了上百个 `Bash` 脚本,但我的 `Bash` 依然写得很糟糕,每一次我都不得不去查一些简单逻辑结构的语法.如 ...

  5. 使用Node.js创建命令行脚本工具

    在我的职业生涯中已经写过了上百个 Bash 脚本,但我的 Bash 依然写得很糟糕,每一次我都不得不去查一些简单逻辑结构的语法.如果我想通过 curl 或者 sed 来做一些事情,我也必须去查找 ma ...

  6. Vue入门教程:node安装vue命令行工具及启动项目

    安装淘宝npm镜像 npm install -g cnpm --registry=https://registry.npm.taobao.org 全局安装vue命令行工具 cnpm install - ...

  7. Node.js 在命令行下执行Console.log()命令时,第二行会打印undefined的原因

    转载:http://blog.csdn.net/chy555chy/article 问题描述:在命令行下执行Console.log()命令后,第一行会以 "正常的白字" 输出log ...

  8. 用node写一个命令行工具

    首先,大家在使用webpack,webpack-dev-server,babel-cli,vue-cli,npm这类工具的时候有没有思考过一个问题? 为什么我全局安装这个模块之后,就能在shell中使 ...

  9. java venus_来认识一下venus-init——一个让你仅需一个命令开始Java开发的命令行工具...

    前言 不知道你是否有过这样的经历.不管你是什么岗位,前端也好,后端也罢,想去了解一下Java开发到底是什么样的,它是不是真的跟传说中的一样. 于是你拿起键盘,用触控板 ? '' : 抄起鼠标',开始了 ...

最新文章

  1. 16岁自闭少年被指黑掉英伟达微软,曾赚1400万美元,英国警方逮捕7人
  2. FPGA之道(44)HDL中的隐患写法
  3. (0055)iOS开发之dealloc认识
  4. OGG重复记录导致复制进程挂起
  5. .NET Core 1.0 RC2 历险之旅
  6. LeetCode 934. 最短的桥(2次BFS)
  7. ACM 2018 Fellow名单公布:李飞飞等多位华人入选,无国内成员
  8. 《springcloud超级入门》Spring Cloud和Dubbo的区别及各自的优缺点《三》
  9. JSPatch真强大!
  10. C++常用函数有哪些?
  11. 【车间调度】基于matlab遗传算法求解混合流水车间调度最优问题【含Matlab源码 901期】
  12. 微分几何笔记(1)——参数曲线、内积、外积
  13. css给div四角添加效果
  14. 中国移动H1S-3光猫破解路由器桥接教程
  15. 前端点击图片将跳出显示框显示图片
  16. c语言编程软件平板_想在ipad上进行C语言程序编写,请问有没有编译的APP
  17. 【打卡-Coggle竞赛学习2023年3月】对话意图识别
  18. JavaScript - V8
  19. vue 修改地址栏参数
  20. 【操作系统】操作系统知识点整理;C++ 实现线程池与windows 线程池的使用;

热门文章

  1. linux 启动openfire
  2. Axon框架使用指南(二):入门
  3. 基于JAVA实现GPG加密解密(Windows+java两种方式)
  4. 台式电脑无法找到网格打印机_台式打印机和专业打印机之间有什么区别?
  5. excel 服务器怎么添加文件,本地的Excel文件怎么导入到远端服务器的临时表中
  6. 王子救公主(DFS)
  7. networkx的使用
  8. 根据LocalDate或者DateUtils计算两个日期之间的天数差
  9. linux下的ld命令(1)
  10. 谷歌验证码reCAPTCHA的运用