首先,我们会使用Node内置的API来学习命令行基础知识;然后我们会使用commander
模块来扩展基础知识。
一.命令行参数
命令行参数是一种基本的方式,来为计算机程序提供输入。在Node应用中,命令行参数可以通过process
这个全局对象的argv
属性来获得(数组类型)。下面的代码展示了如何使用forEach()
这个方法来遍历argv
。
process.argv.forEach(function(arg,index){
console.log("argv["+index+"] ="+ arg);
});
将代码保存在javascript文件中,命名为argv.js。在终端运行程序:
$ node argv.js -foo 3 --bar=4 -baz
argv[0] =/usr/local/bin/node
argv[1] =/Users/xxxx/Desktop/argv.js
argv[2] =-foo
argv[3] =3
argv[4] =--bar=4
argv[5] =-baz
其中,argv数组的前两个元素是不变的:分别是node和js文件的路径。
1.解析参数值:
我们传入了三个参数,分别是:foo、bar、baz;但是它们工作方式都不一样,参数foo的值为3,跟在参数名后面;bar的值为4,与bar处在同一个参数中,跟在=符号后面;而baz是Boolean类型的参数,如果参数被提供则为true,否则为false。
为了提取出正确的命令行参数值,我们可以开发一个自定义的解析器。在下面的代码中,parseArgs()函数负责解析命令行,提取值,返回可以将参数映射成值的对象。
function parseArgs() {
var argv = process.argv;
var args = {
baz : false
};
for (var i = 0,len = argv.length; i < len; i++) {
var arg = argv[i];
var match;
if(arg === "-foo"){
args.foo = parseInt(argv[++i]);
}else if (arg === "-baz"){
args.baz = true;
//使用正则表达式来解析
}else if (match = arg.match(/--bar=(\d+)/)){
args.bar = parseInt(match[1]);
}
}
return args;
}
var args = parseArgs();
console.log(args);
运行结果如下:
$ node argv.js -foo 3 --bar=4 -baz
argv[0] =/usr/local/bin/node
argv[1] =/Users/xxx/Desktop/argv.js
argv[2] =-foo
argv[3] =3
argv[4] =--bar=4
argv[5] =-baz
{ baz: true, foo: 3, bar: 4 }
JavaScript内置的isFinite()方法用来判断参数是否为有效的整数。
2.commander
模块中的命令行参数
第三方模块,commander,用来简化CLI(命令行接口)任务,例如参数解析、读取用户输入。使用npm install commander
来安装commander模块,为了完成命令行参数解析,commander提供了option()
和parse()
方法。每次调用option()
方法都会向commander注册一个合理的命令行参数,所有的参数注册好后,parse()
方法就会用来从命令行中提取参数值。
看下示例代码:
var commander = require("commander");
commander
.option("-f,--foo ","Integer value for foo",parseInt,0)
.option("-b,--bar " ,"Integer value for bar",parseInt,0)
.option("-z,--baz","Boolean argument baz")
.parse(process.argv);
console.log(commander.foo);
console.log(commander.bar);
console.log(commander.baz);
运行效果:
$ node command.js -f 4 -b 3 -z
4
3
true
该程序接收三个参数--foo
、--bar
、--baz
。--foo
参数也可以写成-f
,这是参数的缩写形式。所有的commander
参数必须要有一个缩写名称和一个全称。缩写名称是一个” - “符号加上一个字母。全称是” – “加上名称。
注意,跟在--foo
、--bar
后面的和
,这是跟在参数后面的值。使用尖括号” <> “代表值必须要提供,否则会出现错误;中括号” [] “可以提供,也可以不提供。--baz
是Boolean类型的参数,因为它不会接收任何的参数。跟随参数字符串后面的是描述性字符串。
需要指出的是,--foo
和--bar
也涉及到了parseInt()
函数和数字0。parseInt()
函数作为一个参数被传递到option()函数中,是用来处理额外的参数的,而额外的参数是作为parseInt()
。在上述代码中,--foo
和--bar
的值是整数,如果没有为--foo
和--bar
提供值的话,就被设置为0。
所有的选项被注册后,就调用parse()来处理命令行。一般传入的是process.argv
,解析后,参数的值就可以通过它们的全称来获取。
再看一下自定义的处理额外参数的代码:
var commander = require("commander");
var executeFoo = function (input){
console.log("executeFoo",input);
}
var executeBar = function (input){
console.log("executeBar",input);
}
commander
.option("-f,--foo ","Integer value for foo",executeFoo,"default-foo")
.option("-b,--bar " ,"Integer value for bar",executeBar,"default-bar")
.option("-z,--baz","Boolean argument baz")
.parse(process.argv);
运行结果为:
$ node command.js -f wind -b rain
executeFoo wind
executeBar rain
二.学习Commander
1.选项解析
选项使用.option()方法定义的,也会作为选项的文档。
var program = require("commander");
program
.version('0.0.1')
.option('-p,--peppers','Add peppers')
.option('-p,--pineapple','ADD pineapple')
.option('-b,--bbq-sauce','Add bbq souce')
.option('-c,--cheese [type]','Add the specified type of cheese [marble]','marble')
.parse(process.argv);
console.log('You ordered a pizza with:');
if(program.peppers) console.log('- peppers');
if(program.pineapple) console.log('-pineapple');
if(program.bbqSauce) console.log('-bbq');
console.log(' -%s cheese',program.cheese);
在上述代码中,--bbq-sauce
是驼峰匹配(camel-cased),即bbqSauce。
2.对参数进行处理
var program = require("commander");
function range(val) {
return val.split('..').map(Number);
}
function list(val) {
return val.split(',');
}
function collect(val,memo) {
memo.push(val);
return memo;
}
function increaseVerbosity(v,total) {
return total + 1;
}
program
.version('0.0.1')
.usage('[options] ' )
.option('-i,--integer ' ,'An integer argument',parseInt)
.option('-f,--float ' ,'A float argument',parseFloat)
.option('-r,--range ..','A range',range)
.option('-l,--list ' ,'A list',list)
.option('-o,--optional [value]','An optional value')
.option('-c,--collect [value]','A repeatable value',collect,[])
.option('-v, --verbose','A value that can be in incread',increaseVerbosity,0)
.parse(process.argv);
console.log('int : %j',program.integer);
console.log('float : %j',program.float);
console.log('optional : %j',program.optional);
program.range = program.range || [];
console.log('range: %j..%j',program.range[0],program.range[1]);
console.log('list : %j',program.list);
console.log('collect: %j',program.collect);
console.log('verbosity : %j',program.verbose);
console.log('args:%j',program.args);
运行结果:
$ node command.js -i 2 -f 4.4 -o 3 -r 2..4 -l 1,2,3,4 -c 4 -v
int : 2
float : 4.4
optional : "3"
range: 2..4
list : ["1","2","3","4"]
collect: ["4"]
verbosity : 1
args:[]
3.正则表达式(Regular Expression)
var program = require("commander");
program
.version('0.0.1')
.option('-s --size ' ,'Pizza size',/^(large|medium|small)$/i,'medim')
.option('-d, --drink [drink]','Drink',/^(coke|pepsi|izze)$/i)
.parse(process.argv);
console.log('size : %j',program.size);
console.log('drink: %j',program.drink);
运行结果:
$ node command.js -s large -d
size : "large"
drink: true
$ node command.js -s larg -d cok
size : "medim"
drink: true
4.可变参数(Variadic arguments)
命令的最后一个参数(也只能是最后一个参数)可以是可变参数,为了让一个参数是可变的,你需要在参数名后面添加”...
“。
示例代码如下:
var program = require("commander");
program
.version('0.0.1')
.command('rmdir [otherDirs...]' )
.action(function(dir,otherDirs){
console.log('rmdir %s',dir);
if(otherDirs){
otherDirs.forEach(function (oDir) {
console.log('rmdir %s',oDir);
});
}
});
program.parse(process.argv);
运行结果如下:
$ node command.js rmdir a.txt b.html c.doc
rmdir a.txt
rmdir b.html
rmdir c.doc
5.指定参数语法
var program = require("commander");
program
.version('0.0.1')
.arguments(' [env]' )
.action(function(cmd,env){
cmdValue = cmd;
envValue = env;
});
program.parse(process.argv);
if(typeof cmdValue === 'undefinee') {
console.error('no command given');
procee.exit(1);
}
console.log('command:', cmdValue);
console.log('enviroment:',envValue||"no enviroment given");
运行结果:
$ node command.js love you
command: love
enviroment: you
$ node command.js love
command: love
enviroment: no enviroment given
$ node command.js
/Users/weichuang/Desktop/Node/command.js:19
console.log('command:', cmdValue);
^
ReferenceError: cmdValue is not defined
6.Git样式的子命令
示例代码:
var program = require("commander");
program
.version('0.0.1')
.command('install [name]','install one or more packages')
.command('search [query]','search with optional query')
.command('list','list packages installed',{isDefault:true})
.parse(process.argv);
当.command()
被调用时,不应当调用.action(callback即回调函数)
来处理子命令,否则会出现错误。这会告诉commander
你会使用分开的程序来处理子命令。
commander会在入口脚本所在的那个目录搜索可执行程序,名字格式为”program-command
“,例如,command-install
、command-search
。(其中,command
是程序的名称,install
、search
是子命令)。
选项可以传递给.command()。将opts.noHelp
设置为true
会将选项从产生的帮助输出中移除,将opts.isDefault设置true:如果其它子命令没有设置,这个命令会运行。
如果程序是被全局安装的,确保可执行的程序有正确的模式,例如755。
7.自动产生的帮助
帮助信息是自动生成的,建立在commander对你的程序所了解的情况下,所以下面的--help
信息是自动产生的:
$ node command.js --help
Usage: command [options] [command]
Commands:
install [name] install one or more packages
search [query] search with optional query
list list packages installed
help [cmd] display help for [cmd]
Options:
-h, --help output usage information
-V, --version output the version numbe
8.自定义帮助
你可以通过监听”–help”来展示自定义的-h,--help
信息,一旦完成展示帮助信息,commander就会自动退出程序,例如,下面的”stuff”不会被输出。
var program = require("commander");
program
.version('0.0.1')
.option('-f,--foo','enable some foo')
.option('-b,--bar','enable some bar');
program.on('--help',function(){
console.log('Following is the help information :');
console.log(' $ -f --foo eat foo');
console.log(' $ -b --bar eat bar');
});
program.parse(process.argv);
console.log('stuff');
运行结果:
$ node command.js --help
Usage: command [options]
Options:
-h, --help output usage information
-V, --version output the version number
-f,--foo enable some foo
-b,--bar enable some bar
Following is the help information :
$ -f --foo eat foo
$ -b --bar eat bar
9.”.outputHelp(cb)
”
这种做法会输出帮助信息,但是不会退出程序。可选的回调函数允许在帮助文本展示之前对其进行加工。
var program = require("commander");
var colors = require("colors");
program
.version('0.0.1')
.command('getstream [url]','get stream URL')
.parse(process.argv);
if(!process.argv.slice(2).length){
console.log('red');
program.outputHelp(make_red);
}
function make_red(txt) {
return colors.red(txt);
}
10.示例代码
var program = require("commander");
program
.version('0.0.1')
.option('-C,--chdir ' ,'change the working directory')
.option('-c,--config ,'set config path.defaults to ./deploy.conf')
.option('t,--no-tests','ignore test hook');
program
.command('setup [env]')
.description('run setup commands for all envs')
.option('-s,--setup_mode [mode]','which setup mode to use')
.action(function(env,options){
var mode = options.setup_mode || "normal";
env = env || "all";
console.log('setup for %s env(s) with %s mode',env,mode);
});
program
.command('exec ' )
.alias('ex')
.description('execute the given remote cmd')
.option('-e,--exec_mode ' ,'which setup mode to use')
.action(function(cmd,options){
console.log("exec %s using %s mode", cmd,options.exec_mode);
}).on('--help',function(){
console.log(' exec-help');
console.log('$delpoy exec');
});
program
.command('*')
.action(function(env){
console.log('deploying %s',env);
})
program.parse(process.argv);
运行结果:
$ node command.js --help
Usage: command [options] [command]
Commands:
setup [options] [env] run setup commands for all envs
exec|ex [options] execute the given remote cmd
*
Options:
-h, --help output usage information
-V, --version output the version number
-C,--chdir change the working directory
-c,--config
t,--no-tests ignore test hook
$ node command.js setup hello world
setup for hello env(s) with normal mode
三.从零制作一个可以发布到npm官网的node包
1.创建一个文件夹,命名为remem
,在终端进入该目录下。
2.使用npm init
命令来创建package.json
文件以及完成相关文件的配置。
3.在当前目录下创建一个bin文件夹,并在里面创建一个remem.js文件。
4.在package.json
文件中进行配置,添加:"bin": { "remem": "bin/remem.js" },
5.在制作包时,我们需要两个第三方模块:分别是commander和sqlite3。在终端进入根目录(即package.json所在的目录),通过命令安装这两个模块。
$ sudo npm install sqlite3
$ sudo npm install commander
6.在package.json中配置这两个依赖项,添加:"dependencies":{
"commander":"~2.9.0",
"sqlite3":"~3.1.1"
},
7.这一步当然是写代码,具体根据自己的需求来写….
8.配置全局运行命令
在项目目录(即package.json所在目录)下,运行 npm link
会自动添加全局的 symbolic link
,然后就可以使用自己的命令了。
这是我开发的npm模块remem