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

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

[[169849]]

【引自ideras.me的博客】前言

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

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

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

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

命令行参数解析

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

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

  • minimist 源自
  • optimist 模仿python的optimist项目
  • commander.js tj是node.js大神,co的作者, commander.js源自ruby的commander项目,作者也是tj
  • nopt npm项目中使用
  • nomnom 不再维护,不建议使用

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.  
  4. if (argv.ships > 3 && argv.distance < 53.5) { 
  5.     console.log('Plunder more riffiwobbles!'); 
  6. else { 
  7.     console.log('Retreat from the xupptumblers!'); 
  8.  
  1. $ ./plunder.js --ships=4 --distance=22 
  2. Plunder more riffiwobbles! 
  3.  
  4. $ ./plunder.js --ships 12 --distance 98.7 
  5. 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.  
  7. VERBOSE_LEVEL = argv.verbose; 
  8.  
  9. function WARN()  { VERBOSE_LEVEL >= 0 && console.log.apply(console, arguments); } 
  10. function INFO()  { VERBOSE_LEVEL >= 1 && console.log.apply(console, arguments); } 
  11. function DEBUG() { VERBOSE_LEVEL >= 2 && console.log.apply(console, arguments); } 
  12.  
  13. WARN("Showing only important stuff"); 
  14. INFO("Showing semi-important stuff too"); 
  15. DEBUG("Extra chatty mode"); 

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

  1. $ node count.js 
  2. Showing only important stuff 
  3.  
  4. $ node count.js -v 
  5. Showing only important stuff 
  6. Showing semi-important stuff too 
  7.  
  8. $ node count.js -vv 
  9. Showing only important stuff 
  10. Showing semi-important stuff too 
  11. Extra chatty mode 
  12.  
  13. $ node count.js -v --verbose 
  14. Showing only important stuff 
  15. Showing semi-important stuff too 
  16. 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. 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.  
  7. 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

  1. .command方法有三个接口 
  2.  
  3. .command(cmd, desc, [builder], [handler]) 
  4.  
  5. .command(cmd, desc, [module]) 
  6.  
  7. .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.  
  4. exports.describe = 'make a get HTTP request' 
  5.  
  6. exports.builder = { 
  7.   banana: { 
  8.     default'cool' 
  9.   }, 
  10.   batman: { 
  11.     default'sad' 
  12.   } 
  13.  
  14. exports.handler = function (argv) { 
  15.   // do something with argv. 
  16.  

引入的时候这样使用

  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.  
  4. │ ├── douban 
  5.  
  6. │ │ └── movie.js 
  7.  
  8. │ ├── douban.js 
  9.  
  10. │ ├── gg 
  11.  
  12. │ │ ├── client.js 
  13.  
  14. │ │ ├── login.js 
  15.  
  16. │ │ ├── scope.js 
  17.  
  18. │ │ ├── scope.json 
  19.  
  20. │ │ ├── secret.json 
  21.  
  22. │ │ ├── token.json 
  23.  
  24. │ │ └── upload.js 
  25.  
  26. │ ├── gg.js 
  27.  
  28. │ ├── git 
  29.  
  30. │ │ ├── commit.js 
  31.  
  32. │ │ ├── create.js 
  33.  
  34. │ │ ├── deploy.js 
  35.  
  36. │ │ ├── push.js 
  37.  
  38. │ │ └── token.json 
  39.  
  40. │ ├── git.js 
  41.  
  42. │ ├── gm.js 
  43.  
  44. │ ├── md5.js 
  45.  
  46. │ ├── news 
  47.  
  48. │ │ ├── bing.js 
  49.  
  50. │ │ ├── funs.js 
  51.  
  52. │ │ ├── funs.json 
  53.  
  54. │ │ ├── games.js 
  55.  
  56. │ │ ├── games.json 
  57.  
  58. │ │ ├── google.js 
  59.  
  60. │ │ ├── newsall.json 
  61.  
  62. │ │ ├── shops.js 
  63.  
  64. │ │ ├── shops.json 
  65.  
  66. │ │ ├── videos.js 
  67.  
  68. │ │ └── videos.json 
  69.  
  70. │ └── news.js 
  71.  
  72. └── pit.js 

 pit.js:命令行的入口

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

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

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

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

git.js

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

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

commit.js

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

 这个命令默认运行在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编辑文章,然后发布,对这一类网站你都可以开发自己的命令行统一进行管理,当你写完文章之后,只需要一个简单 的命令,就能把文章同时推送到各大网站

责任编辑:庞桂玉 来源: ideras.me微博
相关推荐

2013-12-30 17:04:00

2024-03-01 19:53:37

PyBuilderPython开发

2023-03-21 07:35:43

2020-11-01 20:00:26

命令行ShellLinux

2024-05-17 17:29:00

CurdlingPython开发

2022-02-16 22:57:57

Mitmproxy抓包工具

2020-12-11 06:44:16

命令行工具开发

2020-12-10 16:16:08

工具代码开发

2019-06-10 15:00:27

node命令行前端

2020-11-05 09:30:59

命令行Linux

2023-06-09 07:45:29

Kuberneteskubectl

2011-05-05 11:03:34

jQueryjavascript

2011-04-29 10:46:32

iPhone开发入门iPhoneiOS

2015-11-17 16:11:07

Code Review

2019-01-18 12:39:45

云计算PaaS公有云

2018-04-18 07:01:59

Docker容器虚拟机

2020-07-02 15:32:23

Kubernetes容器架构

2023-03-31 08:44:55

Go开发命令

2023-11-16 08:53:05

NumPy库Python

2020-11-22 06:20:53

命令行Linux
点赞
收藏

51CTO技术栈公众号