美文网首页全栈之路程序员前端
从零开始打造个人专属命令行工具集——yargs完全指南

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

作者: 蓝猫163 | 来源:发表于2016-08-12 22:28 被阅读3914次

    前言

    使用命令行程序对程序员来说很常见,就算是前端工程师或者开发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的开源项目,这里简单列举一下,有兴趣的可以自己去了解一下,
    然后选择自己喜欢的项目来使用。

    yargs

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

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

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

    简单模式

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

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

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

    示例代码都来自官网:yargs

    简单模式还能读取短变量如-x 4相当于argv.x = 4

    简单模式还能读取布尔类型-s相当于argv.s = true

    简单模式还能读取非-开始的变量,这种类型的变量保存在argv._数组里面

    参数配置

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

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

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

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

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

    $ node count.js
    Showing only important stuff
    
    $ node count.js -v
    Showing only important stuff
    Showing semi-important stuff too
    
    $ node count.js -vv
    Showing only important stuff
    Showing semi-important stuff too
    Extra chatty mode
    
    $ node count.js -v --verbose
    Showing only important stuff
    Showing semi-important stuff too
    Extra chatty mode
    

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

    提示用法

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

    必选参数

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

    提供参数默认值

    #!/usr/bin/env node
    var argv = require('yargs')
        .default('x', 10)
        .default('y', 10)
        .argv
    ;
    console.log(argv.x + argv.y);
    

    打印帮助信息

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

    使用别名

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

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

    参数数组

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

    调用

    node array_test.js -n abc test
    

    设置参数范围

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

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

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

    子命令

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

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

    .command

    .command方法有三个接口

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

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

    使用第一个接口的示例

    yargs
      .command(
        'get',
        'make a get HTTP request',
        function (yargs) {
          return yargs.option('u', {
            alias: 'url',
            describe: 'the URL to make an HTTP request to'
          })
        },
        function (argv) {
          console.log(argv.url)
        }
      )
      .help()
      .argv
    

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

    这是模块的代码

    // my-module.js
    exports.command = 'get <source> [proxy]'
    
    exports.describe = 'make a get HTTP request'
    
    exports.builder = {
      banana: {
        default: 'cool'
      },
      batman: {
        default: 'sad'
      }
    }
    
    exports.handler = function (argv) {
      // do something with argv.
    }
    

    引入的时候这样使用

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

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

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

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

    .commandDir

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

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

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

    .
    ├── pit
    │   ├── douban
    │   │   └── movie.js
    │   ├── douban.js
    │   ├── gg
    │   │   ├── client.js
    │   │   ├── login.js
    │   │   ├── scope.js
    │   │   ├── scope.json
    │   │   ├── secret.json
    │   │   ├── token.json
    │   │   └── upload.js
    │   ├── gg.js
    │   ├── git
    │   │   ├── commit.js
    │   │   ├── create.js
    │   │   ├── deploy.js
    │   │   ├── push.js
    │   │   └── token.json
    │   ├── git.js
    │   ├── gm.js
    │   ├── md5.js
    │   ├── news
    │   │   ├── bing.js
    │   │   ├── funs.js
    │   │   ├── funs.json
    │   │   ├── games.js
    │   │   ├── games.json
    │   │   ├── google.js
    │   │   ├── newsall.json
    │   │   ├── shops.js
    │   │   ├── shops.json
    │   │   ├── videos.js
    │   │   └── videos.json
    │   └── news.js
    └── pit.js
    
    

    pit.js:命令行的入口

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

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

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

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

    git.js

    
    exports.command = 'git <command>';
    
    exports.desc = 'github command list';
    
    exports.builder = function (yargs) {
      return yargs.commandDir('git')
    }
    
    exports.handler = function (argv) {}
    
    

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

    commit.js

    'use strict';
    
    var fs = require('fs');
    var path = require('path');
    
    require('shelljs/global');
    
    var Q = require('q');
    
    function _exec(cmd) {
      var deferred = Q.defer();
      exec(cmd, function (code, stdout, stderr) {
        deferred.resolve();
      });
      return deferred.promise;
    }
    
    exports.command = 'commit';
    
    exports.desc = 'commit repo local';
    
    exports.builder = function (yargs) {
      return yargs
        .help('h');
    };
    
    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`);
        });
    
    }
    

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

    欢迎大家交流自己的想法!

    个人博客: https://ideras.me/

    相关文章

      网友评论

      • e042cbe4da21:这文章本是好文章,可是不知道作者是复制的还是本身不注重格式,导致内容丢失,格式有点乱。。

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

      本文链接:https://www.haomeiwen.com/subject/opyesttx.html