从零开始打造个人专属命令行工具集——yargs 完全指南

前言

使用命令行程序对程序员来说很常见,就算是前端工程师或者开发 GUI 的,也需要使用命令行来编译程序或者打包程序。

熟练使用命令行工具能极大的提高开发效率,Linux 自带的命令行工具都非常的有用,但是这些工具都是按照通用需求开发出来的,如果有一些特别的需求,还是需要自己写脚本来完成一些比如文件批量重命名,文件内容批量替换等任务来提供工作效率。

在 Node.js 出来之前,Python 经常被用来开发一些脚本完成特殊的任务,比如 Python 爬虫,Python 相关的教程有很多,有兴趣的自己 Google。

得益于 Node.js 的异步 io 特性,使用 Node.js 开发 io 密集类任务变得非常简单,这篇文章就为大家讲讲怎么使用 Node.js 的 yargs 模块来开发自己的命令行工具集合。

命令行参数解析

yargs 是一个用来完成命令行参数解析的 npm 模块,回到使用 shell 开发命令行的时代,getopts 是第一代命令行参数解析工具,经过shell => python => node.js 的迭代,命令行参数解析程序其实没有多大的进化,它们的目的始终是把用户从命令行传入的参数解析成指定的格式,供程序使用。

虽然没有多大变化,但是由于开发一个命令行参数解析模块比较简单,所以目前 Node.js 社区存在很多类似 yargs 的开源项目,这里简单列举一下,有兴趣的可以自己去了解一下, 然后选择自己喜欢的项目来使用。

yargs

读过阮一峰的 Node.js 命令行程序开发教程之后开始使用 yargs 开发自己命令行工具, 用过一段时间发现非常的好用。

自阮大神的文章发布以来,yargs 有了一些改动,添加有很多有用的功能,特别是.commandDir(directory, [opts])这个功能,对打造命令行工具集合非常有用,所以写一个新版本的 yargs 教程还是有必要的。

yargs 的用法还算比较简单,对英文有自信的可以去首页阅读原版:yargs

简单模式

yargs 默认使用两个--作为参数的前缀,参数名与参数值中间使用空格或者=分隔都可以。

下面的代码展示了 yargs 最简单的用法,你只需要引入 yargs,就能读取命令行参数,不需要写任何的配置,非常的简单。

 
 
  1. #!/usr/bin/env node
  2. var argv = require('yargs').argv;
  3. if (argv.ships > 3 && argv.distance < 53.5) {
  4. console.log('Plunder more riffiwobbles!');
  5. } else {
  6. console.log('Retreat from the xupptumblers!');
  7. }
 
 
  1. $ ./plunder.js --ships=4 --distance=22
  2. Plunder more riffiwobbles!
  3. $ ./plunder.js --ships 12 --distance 98.7
  4. Retreat from the xupptumblers!

示例代码都来自官网:yargs

简单模式还能读取短变量如-x 4相当于argv.x = 4。简单模式还能读取布尔类型-s相当于argv.s = true。简单模式还能读取非-开始的变量,这种类型的变量保存在argv._数组里面。

参数配置

简单模式的功能都只用一行代码就能实现:

 
 
  1. var argv = require('yargs').argv;

但是如果你想统计变量出现的次数怎么办? 答案就是添加参数配置选项。

 
 
  1. #!/usr/bin/env node
  2. var argv = require('yargs')
  3. .count('verbose')
  4. .alias('v', 'verbose')
  5. .argv;
  6. VERBOSE_LEVEL = argv.verbose;
  7. function WARN() { VERBOSE_LEVEL >= 0 && console.log.apply(console, arguments); }
  8. function INFO() { VERBOSE_LEVEL >= 1 && console.log.apply(console, arguments); }
  9. function DEBUG() { VERBOSE_LEVEL >= 2 && console.log.apply(console, arguments); }
  10. WARN("Showing only important stuff");
  11. INFO("Showing semi-important stuff too");
  12. DEBUG("Extra chatty mode");

上面的程序能统计verbose参数出现的次数,缩写-v也会统计进去,具体调用例子参考下面的代码:

 
 
  1. $ node count.js
  2. Showing only important stuff
  3. $ node count.js -v
  4. Showing only important stuff
  5. Showing semi-important stuff too
  6. $ node count.js -vv
  7. Showing only important stuff
  8. Showing semi-important stuff too
  9. Extra chatty mode
  10. $ node count.js -v --verbose
  11. Showing only important stuff
  12. Showing semi-important stuff too
  13. Extra chatty mode

yargs 提供很多接口用来帮助完善命令行程序:

提示用法:

 
 
  1. var argv = require('yargs')
  2. .usage('Usage: $0 -w [num] -h [num]')
  3. .argv;

必选参数:

 
 
  1. #!/usr/bin/env node
  2. var argv = require('yargs')
  3. .usage('Usage: $0 -w [num] -h [num]')
  4. .demand(['w','h'])
  5. .argv;

提供参数默认值:

 
 
  1. #!/usr/bin/env node
  2. var argv = require('yargs')
  3. .default('x', 10)
  4. .default('y', 10)
  5. .argv
  6. ;
  7. console.log(argv.x + argv.y);

打印帮助信息:

 
 
  1. #!/usr/bin/env node
  2. var argv = require('yargs')
  3. .usage('Usage: $0 <command> [options]')
  4. .help('h')
  5. .alias('h', 'help')
  6. .epilog('copyright 2015')
  7. .argv;

使用别名:

 
 
  1. var argv = require('yargs')
  2. .usage('Usage: $0 <command> [options]')
  3. .alias('h', 'help')
  4. .argv;

访问argv.h相当于访问argv.help

参数数组:

 
 
  1. var argv = require('yargs')
  2. .usage('Usage: $0 <command> [options]')
  3. .alias('n', 'name')
  4. .array('n')
  5. .argv;
  6. console.log(argv.n);

调用:

 
 
  1. node array_test.js -n abc test

设置参数范围:

 
 
  1. var argv = require('yargs')
  2. .alias('i', 'ingredient')
  3. .describe('i', 'choose your sandwich ingredients')
  4. .choices('i', ['peanut-butter', 'jelly', 'banana', 'pickles'])
  5. .help('help')
  6. .argv

上述代码设定argv.i的值只能是['peanut-butter', 'jelly', 'banana', 'pickles']数组中的一个。

上面是yargs比较简单的用法,如果想阅读完整版,建议去github上阅读。

子命令

yargs 适合开发复杂的命令行程序的另一个原因是它支持子命令,而且子命令可以嵌套,这意味着你也可以开发出类似 git 这样拥有上百个命令的程序

yargs 的子命令有两种模式:.command(*).commandDir(directory, [opts])

.command

.command方法有三个接口:

 
 
  1. .command(cmd, desc, [builder], [handler])
  2. .command(cmd, desc, [module])
  3. .command(module)

其实它们的用法都差不多,可以把它们都看作传递一个 module 给 yargs,这个 module 必须导出四个变量cmd, desc [builder], [handler],其中 builder 和 handler 是方法,另外两个是字符串。

使用第一个接口的示例:

 
 
  1. yargs
  2. .command(
  3. 'get',
  4. 'make a get HTTP request',
  5. function (yargs) {
  6. return yargs.option('u', {
  7. alias: 'url',
  8. describe: 'the URL to make an HTTP request to'
  9. })
  10. },
  11. function (argv) {
  12. console.log(argv.url)
  13. }
  14. )
  15. .help()
  16. .argv

使用第三个接口需要把这个模块在单独的文件,然后用 require 引入。

这是模块的代码:

 
 
  1. // my-module.js
  2. exports.command = 'get <source> [proxy]'
  3. exports.describe = 'make a get HTTP request'
  4. exports.builder = {
  5. banana: {
  6. default: 'cool'
  7. },
  8. batman: {
  9. default: 'sad'
  10. }
  11. }
  12. exports.handler = function (argv) {
  13. // do something with argv.
  14. }

引入的时候这样使用:

 
 
  1. yargs.command(require('my-module'))
  2. .help()
  3. .argv

当额外的模块没有定义 cmd 和 desc 的时候可以使用第二个接口:

 
 
  1. yargs.command('get <source> [proxy]', 'make a get HTTP request', require('my-module'))
  2. .help()
  3. .argv

这里建议使用第三个接口,这样能保持模块的内聚,这种模块你能挂载在任何命令下面,迁移的时候不需要修改模块代码,只需要修改引入模块的代码就能实现

.commandDir

如果有大量的命令都使用上面的.command(module)来开发的话,这些模块都有相同的结构,应该能有方法简化这些命令的引入过程,把这个过程自动化,基于 这个目的 yargs 提供了.commandDir接口

下面参考一个我自己写的项目 pit

下面是这个项目的目录结构:

 
 
  1. .
  2. ├── pit
  3. ├── douban
  4. └── movie.js
  5. ├── douban.js
  6. ├── gg
  7. ├── client.js
  8. ├── login.js
  9. ├── scope.js
  10. ├── scope.json
  11. ├── secret.json
  12. ├── token.json
  13. └── upload.js
  14. ├── gg.js
  15. ├── git
  16. ├── commit.js
  17. ├── create.js
  18. ├── deploy.js
  19. ├── push.js
  20. └── token.json
  21. ├── git.js
  22. ├── gm.js
  23. ├── md5.js
  24. ├── news
  25. ├── bing.js
  26. ├── funs.js
  27. ├── funs.json
  28. ├── games.js
  29. ├── games.json
  30. ├── google.js
  31. ├── newsall.json
  32. ├── shops.js
  33. ├── shops.json
  34. ├── videos.js
  35. └── videos.json
  36. └── news.js
  37. └── pit.js

pit.js:命令行的入口

 
 
  1. #!/usr/bin/env node
  2. require('yargs')
  3. .commandDir('pit')
  4. .demand(1)
  5. .help()
  6. .locale('en')
  7. .showHelpOnFail(true, 'Specify --help for available options')
  8. .argv

这段代码只指定读取同目录下同名文件夹pit下面的命令加载为子命令。

注意:commandDir 默认只会加载目录下第一级的文件,不会递归加载,如果想递归加载需要这样写.commandDir('pit', {recurse: true})

接着来看 git 子命令,因为 git 项目每次提交都要重复几个相同的步骤,所有想开发一个更简单的命令进行打包提交。

git.js

 
 
  1. exports.command = 'git <command>';
  2. exports.desc = 'github command list';
  3. exports.builder = function (yargs) {
  4. return yargs.commandDir('git')
  5. }
  6. exports.handler = function (argv) {}

git 也是加载一个目录作为自己的子命令:以 commit 为例

commit.js

 
 
  1. 'use strict';
  2. var fs = require('fs');
  3. var path = require('path');
  4. require('shelljs/global');
  5. var Q = require('q');
  6. function _exec(cmd) {
  7. var deferred = Q.defer();
  8. exec(cmd, function (code, stdout, stderr) {
  9. deferred.resolve();
  10. });
  11. return deferred.promise;
  12. }
  13. exports.command = 'commit';
  14. exports.desc = 'commit repo local';
  15. exports.builder = function (yargs) {
  16. return yargs
  17. .help('h');
  18. };
  19. exports.handler = function (argv) {
  20. var repo = process.cwd();
  21. var name = path.basename(repo);
  22. Q.fcall(function () { })
  23. .then(() => _exec(`git add .`))
  24. .then(() => _exec(`git commit -m 'd'`))
  25. .catch(function (err) {
  26. console.log(err);
  27. })
  28. .done(() => {
  29. console.log(`commit ${repo} done`);
  30. });
  31. }

这个命令默认运行在 git 项目的根目录,和 git 命令不太一样,git 可以在项目根目录下的任意子目录里面运行。

使用 shelljs 来运行子命令,然后用 Q 进行 promise 封装,保证命令的执行顺序,同时把命令行输出和错误信息都打印到控制。

一个很简单能节省时间的命令行程序,作为抛砖引玉之用。

延伸

高手都是擅长使用命令行(电影里面的高手也一样),当你习惯使用命令行完成日常任务之后,慢慢的会形成一种依赖。继续下去,你会考虑把所有的事情都用来命令行来完成,当然这个 目的不能实现,因为能自动完成所有任务的命令行不叫命令行——它叫 AI。

虽然不能开发一台高智能 AI,但是还是有很多任务能用命令行来完成的,这里写下我的思路,供大家参考。

api 命令行

大型网站都提供自己的 api 接口配上 oauth2.0 认证,如果你想使用命令行来调用这些 api 接口,你完全可以做到。

像 aws,google cloud,aliyun 这种云主机,使用命令行能节省很多运维的时间。

另外你也可以参考上面 pit.js 写的 douban.js 来抓取豆瓣的数据,豆瓣的公共 api 不需要认证就能访问,用来做一些测试非常方便。

命令行爬虫

使用 Node.js 开发爬虫就像使用 Python 一样简单,但是一个功能齐全的爬虫必然少不了命令行接口,你不可能每次有新的需求都来修改代码,下次再给大家分享我写的一个简单的基于 Node.js 的爬虫项目。

表单提交

对一些不提供 api 接口但是又想使用命令来进行交互的网站,你可以使用表单提交来进行登录,然后做一些登录之后才能做的事情:例如发表文章。

现在很多的网站都支持使用 markdown 编辑文章,然后发布,对这一类网站你都可以开发自己的命令行统一进行管理,当你写完文章之后,只需要一个简单 的命令,就能把文章同时推送到各大网站。

原文发布时间为:2016-08-28

本文来自云栖社区合作伙伴“Linux中国”

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值