從零開(kāi)始打造個(gè)人專(zhuān)屬命令行工具集——yargs完全指南

前言

使用命令行程序?qū)Τ绦騿T來(lái)說(shuō)很常見(jiàn)寡具,就算是前端工程師或者開(kāi)發(fā)gui的慌随,也需要使用命令行來(lái)編譯程序或者打包程序

熟練使用命令行工具能極大的提高開(kāi)發(fā)效率舱权,linux自帶的命令行工具都非常的有用镐确,但是這些工具都是按照通用需求開(kāi)發(fā)出來(lái)的
黍聂,如果有一些特別的需求,還是需要自己寫(xiě)腳本來(lái)完成一些比如文件批量重命名瓮恭,文件內(nèi)容批量替換等任務(wù)來(lái)提供工作效率雄坪。

在node.js出來(lái)之前,python經(jīng)常被用來(lái)開(kāi)發(fā)一些腳本完成特殊的任務(wù)屯蹦,比如python爬蟲(chóng)维哈,python相關(guān)的教程有很多,有興趣的自己google登澜。

得益于node.js的異步io特性阔挠,使用node開(kāi)發(fā)io密集類(lèi)任務(wù)變得非常簡(jiǎn)單,這篇文章就為大家講講怎么使用node.js的yargs模塊來(lái)開(kāi)發(fā)自己的命令行工具集合脑蠕。

命令行參數(shù)解析

yargs是一個(gè)npm模塊用來(lái)完成命令行參數(shù)解析的购撼,回到使用shell開(kāi)發(fā)命令行的時(shí)代跪削,getopts是第一代命令行參數(shù)解析工具,經(jīng)過(guò)shell => python => node.js
的迭代迂求,命令行參數(shù)解析程序其實(shí)沒(méi)有多大的進(jìn)化碾盐,它們的目的始終是把用戶(hù)從命令行傳入的參數(shù)解析成指定的格式,供程序使用

雖然沒(méi)有多大變化揩局,但是由于開(kāi)發(fā)一個(gè)命令行參數(shù)解析模塊比較簡(jiǎn)單毫玖,所以目前node社區(qū)存在很多類(lèi)似yargs的開(kāi)源項(xiàng)目,這里簡(jiǎn)單列舉一下谐腰,有興趣的可以自己去了解一下孕豹,
然后選擇自己喜歡的項(xiàng)目來(lái)使用。

  • minimist 源自

  • optimist 模仿python的optimist項(xiàng)目

  • commander.js tj是node.js大神十气,co的作者, commander.js源自ruby的commander項(xiàng)目励背,作者也是tj

  • nopt npm項(xiàng)目中使用

  • nomnom 不再維護(hù),不建議使用

yargs

讀過(guò)阮一峰的Node.js 命令行程序開(kāi)發(fā)教程之后開(kāi)始使用yargs開(kāi)發(fā)自己命令行工具砸西,
用過(guò)一段時(shí)間發(fā)現(xiàn)非常的好用叶眉。

自阮大神的文章發(fā)布以來(lái),yargs有了一些改動(dòng)芹枷,添加有很多有用的功能衅疙,特別是.commandDir(directory, [opts])這個(gè)功能,對(duì)打造命令行工具集合非常有用鸳慈,所以寫(xiě)一個(gè)新版本的yargs教程還是有必要的饱溢。

yargs的用法還算比較簡(jiǎn)單,對(duì)英文有自信的可以去首頁(yè)閱讀原版:yargs

簡(jiǎn)單模式

yargs默認(rèn)使用兩個(gè)--作為參數(shù)的前綴走芋,中間使用空格或者=都可以

下面的代碼展示了yargs最簡(jiǎn)單的用法绩郎,你只需要引入yargs,就能讀取命令行參數(shù)翁逞,不需要寫(xiě)任何的配置肋杖,非常的簡(jiǎn)單

#!/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!

示例代碼都來(lái)自官網(wǎng):yargs

簡(jiǎn)單模式還能讀取短變量如-x 4相當(dāng)于argv.x = 4

簡(jiǎn)單模式還能讀取布爾類(lèi)型-s相當(dāng)于argv.s = true

簡(jiǎn)單模式還能讀取非-開(kāi)始的變量,這種類(lèi)型的變量保存在argv._數(shù)組里面

參數(shù)配置

簡(jiǎn)單模式的功能都只用一行代碼就能實(shí)現(xiàn)

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

但是如果你想統(tǒng)計(jì)變量出現(xiàn)的次數(shù)怎么辦挖函? 答案就是添加參數(shù)配置選項(xiàng)状植。

#!/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");

上面的程序能統(tǒng)計(jì)verbose參數(shù)出現(xiàn)的次數(shù),縮寫(xiě)-v也會(huì)統(tǒng)計(jì)進(jìn)去怨喘,具體調(diào)用例子參考下面的代碼

$ 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提供很多接口用來(lái)幫助完善命令行程序津畸,

提示用法

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

必選參數(shù)

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

提供參數(shù)默認(rèn)值

#!/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;

訪問(wèn)argv.h相當(dāng)于訪問(wèn)argv.help

參數(shù)數(shù)組

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

console.log(argv.n);

調(diào)用

node array_test.js -n abc test

設(shè)置參數(shù)范圍

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

上述代碼設(shè)定argv.i的值只能是['peanut-butter', 'jelly', 'banana', 'pickles']數(shù)組中的一個(gè)

上面是yargs比較簡(jiǎn)單的用法,如果想閱讀完整版必怜,建議去github上閱讀

子命令

yargs適合開(kāi)發(fā)復(fù)雜的命令行程序的另一個(gè)原因是它支持子命令洼畅,而且子命令可以嵌套,這意味著你也可以開(kāi)發(fā)出類(lèi)似git這樣擁有上百個(gè)命令的程序

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

.command

.command方法有三個(gè)接口

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

.command(cmd, desc, [module])

.command(module)

其實(shí)它們的用法都差不多棚赔,可以把它們都看作傳遞一個(gè)module給yargs,這個(gè)module必須導(dǎo)出四個(gè)變量
cmd, desc [builder], [handler],其中builder和handler是方法靠益,另外兩個(gè)是字符串

使用第一個(gè)接口的示例

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

使用第三個(gè)接口需要把這個(gè)模塊在單獨(dú)的文件丧肴,然后用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.
}

引入的時(shí)候這樣使用

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

當(dāng)額外的模塊沒(méi)有定義cmd和desc的時(shí)候可以使用第二個(gè)接口

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

這里建議使用第三個(gè)接口,這樣能保持模塊的內(nèi)聚胧后,這種模塊你能掛載在任何命令下面芋浮,遷移的時(shí)候不需要修改模塊代碼,只需要修改引入模塊的代碼就能實(shí)現(xiàn)

.commandDir

如果有大量的命令都使用上面的.command(module)來(lái)開(kāi)發(fā)的話(huà)壳快,這些模塊都有相同的結(jié)構(gòu)纸巷,應(yīng)該能有方法簡(jiǎn)化這些命令的引入過(guò)程,把這個(gè)過(guò)程自動(dòng)化眶痰,基于
這個(gè)目的yargs提供了.commandDir接口

下面參考一個(gè)我自己寫(xiě)的項(xiàng)目pit

下面是這個(gè)項(xiàng)目的目錄結(jié)構(gòu)

.
├── 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默認(rèn)只會(huì)加載目錄下第一級(jí)的文件瘤旨,不會(huì)遞歸加載,如果想遞歸加載需要這樣寫(xiě).commandDir('pit', {recurse: true})

接著來(lái)看git子命令竖伯,因?yàn)間it項(xiàng)目每次提交都要重復(fù)幾個(gè)相同的步驟存哲,所有想開(kāi)發(fā)一個(gè)更簡(jiǎn)單的命令進(jìn)行打包提交

git.js


exports.command = 'git <command>';

exports.desc = 'github command list';

exports.builder = function (yargs) {
  return yargs.commandDir('git')
}

exports.handler = function (argv) {}

git也是加載一個(gè)目錄作為自己的子命令:以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`);
    });

}

這個(gè)命令默認(rèn)運(yùn)行在git項(xiàng)目的根目錄,和git命令不太一樣七婴,git可以在項(xiàng)目根目錄下的任意子目錄里面運(yùn)行祟偷。

使用shelljs來(lái)運(yùn)行子命令,然后用Q進(jìn)行promise封裝打厘,保證命令的執(zhí)行順序修肠,同時(shí)把命令行輸出和錯(cuò)誤信息都打印到
控制。

一個(gè)很簡(jiǎn)單能節(jié)省時(shí)間的命令行程序户盯,作為拋磚引玉之用

延伸

高手都是擅長(zhǎng)使用命令行(電影里面的高手也一樣)嵌施,當(dāng)你習(xí)慣使用命令行完成日常任務(wù)之后,慢慢的會(huì)形成一種依賴(lài)先舷。繼續(xù)下去艰管,你會(huì)考慮把所有的事情都用來(lái)命令行來(lái)完成,當(dāng)然這個(gè)
目的不能實(shí)現(xiàn)蒋川,因?yàn)槟茏詣?dòng)完成所有任務(wù)的命令行不叫命令行——它叫AI

雖然不能開(kāi)發(fā)一臺(tái)高智能ai牲芋,但是還是有很多任務(wù)能用命令行來(lái)完成的,這里寫(xiě)下我的思路捺球,供大家參考

api命令行

大型網(wǎng)站都提供自己的api接口配上oauth2.0認(rèn)證缸浦,如果你想使用命令行來(lái)調(diào)用這些api接口,你完全可以做到

像aws氮兵,google cloud裂逐,aliyun這種云主機(jī),使用命令行能節(jié)省很多運(yùn)維的時(shí)間

另外你也可以參考上面pit.js寫(xiě)的douban.js來(lái)抓取豆瓣的數(shù)據(jù)泣栈,豆瓣的公共api不需要認(rèn)證就能訪問(wèn)卜高,用來(lái)做一些測(cè)試非常方便

命令行爬蟲(chóng)

使用node.js開(kāi)發(fā)爬蟲(chóng)就像使用python一樣簡(jiǎn)單弥姻,但是一個(gè)功能齊全的爬蟲(chóng)必然少不了命令行接口,你不可能每次有新的需求都來(lái)修改代碼掺涛,下次再給大家分享我寫(xiě)的一個(gè)簡(jiǎn)單的基于
node.js的爬蟲(chóng)項(xiàng)目

表單提交

對(duì)一些不提供api接口但是又想使用命令來(lái)進(jìn)行交互的網(wǎng)站庭敦,你可以使用表單提交來(lái)進(jìn)行登錄,然后做一些登錄之后才能做的事情:例如發(fā)表文章

現(xiàn)在很多的網(wǎng)站都支持使用markdown編輯文章薪缆,然后發(fā)布秧廉,對(duì)這一類(lèi)網(wǎng)站你都可以開(kāi)發(fā)自己的命令行統(tǒng)一進(jìn)行管理,當(dāng)你寫(xiě)完文章之后拣帽,只需要一個(gè)簡(jiǎn)單
的命令疼电,就能把文章同時(shí)推送到各大網(wǎng)站

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

個(gè)人博客: https://ideras.me/

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末减拭,一起剝皮案震驚了整個(gè)濱河市蔽豺,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌峡谊,老刑警劉巖茫虽,帶你破解...
    沈念sama閱讀 222,104評(píng)論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異既们,居然都是意外死亡濒析,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,816評(píng)論 3 399
  • 文/潘曉璐 我一進(jìn)店門(mén)啥纸,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)号杏,“玉大人,你說(shuō)我怎么就攤上這事斯棒《苤拢” “怎么了?”我有些...
    開(kāi)封第一講書(shū)人閱讀 168,697評(píng)論 0 360
  • 文/不壞的土叔 我叫張陵荣暮,是天一觀的道長(zhǎng)庭惜。 經(jīng)常有香客問(wèn)我,道長(zhǎng)穗酥,這世上最難降的妖魔是什么护赊? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 59,836評(píng)論 1 298
  • 正文 為了忘掉前任,我火速辦了婚禮砾跃,結(jié)果婚禮上骏啰,老公的妹妹穿的比我還像新娘。我一直安慰自己抽高,他們只是感情好判耕,可當(dāng)我...
    茶點(diǎn)故事閱讀 68,851評(píng)論 6 397
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著翘骂,像睡著了一般壁熄。 火紅的嫁衣襯著肌膚如雪帚豪。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書(shū)人閱讀 52,441評(píng)論 1 310
  • 那天请毛,我揣著相機(jī)與錄音志鞍,去河邊找鬼方仿。 笑死统翩,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的委粉。 我是一名探鬼主播贾节,決...
    沈念sama閱讀 40,992評(píng)論 3 421
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼栗涂,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼斤程!你這毒婦竟也來(lái)了忿墅?” 一聲冷哼從身側(cè)響起沮峡,我...
    開(kāi)封第一講書(shū)人閱讀 39,899評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤邢疙,失蹤者是張志新(化名)和其女友劉穎秘症,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體役耕,經(jīng)...
    沈念sama閱讀 46,457評(píng)論 1 318
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡瞬痘,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,529評(píng)論 3 341
  • 正文 我和宋清朗相戀三年察绷,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了津辩。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片喘沿。...
    茶點(diǎn)故事閱讀 40,664評(píng)論 1 352
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡莺禁,死狀恐怖窄赋,靈堂內(nèi)的尸體忽然破棺而出忆绰,到底是詐尸還是另有隱情较木,我是刑警寧澤,帶...
    沈念sama閱讀 36,346評(píng)論 5 350
  • 正文 年R本政府宣布,位于F島的核電站萎馅,受9級(jí)特大地震影響虹蒋,放射性物質(zhì)發(fā)生泄漏魄衅。R本人自食惡果不足惜晃虫,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 42,025評(píng)論 3 334
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望呻惕。 院中可真熱鬧滥比,春花似錦盲泛、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 32,511評(píng)論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至讥蟆,卻和暖如春瘸彤,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背愕宋。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 33,611評(píng)論 1 272
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留邻寿,地道東北人视哑。 一個(gè)月前我還...
    沈念sama閱讀 49,081評(píng)論 3 377
  • 正文 我出身青樓蒜撮,卻偏偏與公主長(zhǎng)得像淀弹,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子菌赖,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,675評(píng)論 2 359

推薦閱讀更多精彩內(nèi)容