熟练使用命令行工具能极大的提高开发效率,linux自带的命令行工具都非常的有用,但是这些工具都是按照通用需求开发出来的 ,如果有一些特别的需求,还是需要自己写脚本来完成一些比如文件批量重命名,文件内容批量替换等任务来提供工作效率。
【引自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,就能读取命令行参数,不需要写任何的配置,非常的简单
- #!/usr/bin/envnode
- varargv=require('yargs').argv;
- if(argv.ships>3&&argv.distance<53.5){
- console.log('Plundermoreriffiwobbles!');
- }else{
- console.log('Retreatfromthexupptumblers!');
- }
- $./plunder.js--ships=4--distance=22
- Plundermoreriffiwobbles!
- $./plunder.js--ships12--distance98.7
- Retreatfromthexupptumblers!
示例代码都来自官网:yargs
简单模式还能读取短变量如-x 4相当于argv.x = 4
简单模式还能读取布尔类型-s相当于argv.s = true
简单模式还能读取非-开始的变量,这种类型的变量保存在argv._数组里面
参数配置
简单模式的功能都只用一行代码就能实现
- varargv=require('yargs').argv;
但是如果你想统计变量出现的次数怎么办? 答案就是添加参数配置选项。
- #!/usr/bin/envnode
- varargv=require('yargs')
- .count('verbose')
- .alias('v','verbose')
- .argv;
- VERBOSE_LEVEL=argv.verbose;
- functionWARN(){VERBOSE_LEVEL>=0&&console.log.apply(console,arguments);}
- functionINFO(){VERBOSE_LEVEL>=1&&console.log.apply(console,arguments);}
- functionDEBUG(){VERBOSE_LEVEL>=2&&console.log.apply(console,arguments);}
- WARN("Showingonlyimportantstuff");
- INFO("Showingsemi-importantstufftoo");
- DEBUG("Extrachattymode");
上面的程序能统计verbose参数出现的次数,缩写-v也会统计进去,具体调用例子参考下面的代码
- $nodecount.js
- Showingonlyimportantstuff
- $nodecount.js-v
- Showingonlyimportantstuff
- Showingsemi-importantstufftoo
- $nodecount.js-vv
- Showingonlyimportantstuff
- Showingsemi-importantstufftoo
- Extrachattymode
- $nodecount.js-v--verbose
- Showingonlyimportantstuff
- Showingsemi-importantstufftoo
- Extrachattymode
yargs提供很多接口用来帮助完善命令行程序,
提示用法
- varargv=require('yargs')
- .usage('Usage:$0-w[num]-h[num]')
- .argv;
必选参数
- #!/usr/bin/envnode
- varargv=require('yargs')
- .usage('Usage:$0-w[num]-h[num]')
- .demand(['w','h'])
- .argv;
提供参数默认值
- #!/usr/bin/envnode
- varargv=require('yargs')
- .default('x',10)
- .default('y',10)
- .argv
- ;
- console.log(argv.x+argv.y);
打印帮助信息
- #!/usr/bin/envnode
- varargv=require('yargs')
- .usage('Usage:$0<command>[options]')
- .help('h')
- .alias('h','help')
- .epilog('copyright2015')
- .argv;
使用别名
- varargv=require('yargs')
- .usage('Usage:$0<command>[options]')
- .alias('h','help')
- .argv;
访问argv.h相当于访问argv.help
参数数组
- varargv=require('yargs')
- .usage('Usage:$0<command>[options]')
- .alias('n','name')
- .array('n')
- .argv;
- console.log(argv.n);
调用
- nodearray_test.js-nabctest
设置参数范围
- varargv=require('yargs')
- .alias('i','ingredient')
- .describe('i','chooseyoursandwichingredients')
- .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',
- 'makeagetHTTPrequest',
- function(yargs){
- returnyargs.option('u',{
- alias:'url',
- describe:'theURLtomakeanHTTPrequestto'
- })
- },
- function(argv){
- console.log(argv.url)
- }
- )
- .help()
- .argv
使用第三个接口需要把这个模块在单独的文件,然后用require引入
这是模块的代码
- //my-module.js
- exports.command='get<source>[proxy]'
- exports.describe='makeagetHTTPrequest'
- exports.builder={
- banana:{
- default:'cool'
- },
- batman:{
- default:'sad'
- }
- }
- exports.handler=function(argv){
- //dosomethingwithargv.
- }
引入的时候这样使用
- yargs.command(require('my-module'))
- .help()
- .argv
当额外的模块没有定义cmd和desc的时候可以使用第二个接口
- yargs.command('get<source>[proxy]','makeagetHTTPrequest',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/envnode
- require('yargs')
- .commandDir('pit')
- .demand(1)
- .help()
- .locale('en')
- .showHelpOnFail(true,'Specify--helpforavailableoptions')
- .argv
- ···
这段代码只指定读取同目录下同名文件夹`pit`下面的命令加载为子命令
> **注意**:commandDir默认只会加载目录下***级的文件,不会递归加载,如果想递归加载需要这样写`.commandDir('pit', {recurse: true})`
接着来看git子命令,因为git项目每次提交都要重复几个相同的步骤,所有想开发一个更简单的命令进行打包提交
git.js
- exports.command='git';
- exports.desc='githubcommandlist';
- exports.builder=function(yargs){returnyargs.commandDir('git')}
- exports.handler=function(argv){}
git也是加载一个目录作为自己的子命令:以commit为例
commit.js
- 'usestrict';
- varfs=require('fs');varpath=require('path');
- require('shelljs/global');
- varQ=require('q');
- function_exec(cmd){vardeferred=Q.defer();exec(cmd,function(code,stdout,stderr){deferred.resolve();});returndeferred.promise;}
- exports.command='commit';
- exports.desc='commitrepolocal';
- exports.builder=function(yargs){returnyargs.help('h');};
- exports.handler=function(argv){varrepo=process.cwd();varname=path.basename(repo);Q.fcall(function(){}).then(()=>_exec(gitadd.)).then(()=>_exec(gitcommit-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编辑文章,然后发布,对这一类网站你都可以开发自己的命令行统一进行管理,当你写完文章之后,只需要一个简单 的命令,就能把文章同时推送到各大网站
©本文为清一色官方代发,观点仅代表作者本人,与清一色无关。清一色对文中陈述、观点判断保持中立,不对所包含内容的准确性、可靠性或完整性提供任何明示或暗示的保证。本文不作为投资理财建议,请读者仅作参考,并请自行承担全部责任。文中部分文字/图片/视频/音频等来源于网络,如侵犯到著作权人的权利,请与我们联系(微信/QQ:1074760229)。转载请注明出处:清一色财经