zoukankan      html  css  js  c++  java
  • 从零开始打造个人专属命令行工具集——yargs完全指南

    前言

    使用命令行程序对程序员来说很常见,就算是前端工程师或者开发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编辑文章,然后发布,对这一类网站你都可以开发自己的命令行统一进行管理,当你写完文章之后,只需要一个简单
    的命令,就能把文章同时推送到各大网站

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

  • 相关阅读:
    LeetCode Best Time to Buy and Sell Stock
    LeetCode Scramble String
    LeetCode Search in Rotated Sorted Array II
    LeetCode Gas Station
    LeetCode Insertion Sort List
    LeetCode Maximal Rectangle
    Oracle procedure
    浏览器下载代码
    Shell check IP
    KVM- 存储池配置
  • 原文地址:https://www.cnblogs.com/bymax/p/5748662.html
Copyright © 2011-2022 走看看