## 命令行参数处理
在使用其他命令行程序的过程中(如`npm`),我们经常会使用`--help`命令来查询参数的用法,在输入错误的参数时,还会有提示信息。这些,都是我们在开发自己命令中需要考虑的。我们可以使用`process.argv`属性来获取用户从命令行输入的参数,然后,再自己处理各个参数。聪明如你,一定会想到这么普遍的用法肯定有第三方的模块可以简化处理过程。而这就是我们本章要介绍的模块——[commander](https://www.npmjs.com/package/commander)
[TOC]
### 安装
全局安装
~~~
$ npm install commander -g
~~~
本地安装
~~~
$ npm install commander --save
~~~
### commander的使用说明
`commander`的使用官网提供了几个常用的例子,不过,官网写得比较简洁,不适合新手阅读。这里,我将在例子上加上必要的注释,为了简洁,前面的例子已经注释说明的,后面的例子就不再赘述。
#### 参数解析(parse方法的使用)
下面这个例子的关注点放在`commander`的解析流程上,`commander`的使用流程如下:
1.使用`option()`方法自定义参数;
2.使用`parse()`方法解析用户从命令行输入的参数。
~~~
//index.js
//引用commander模块,这里返回的是一个commander对象
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 sauce')
.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);
~~~
`parse()`方法先对`option()`方法定义的参数进行赋值,然后将剩下的参数(未定义的参数)赋值给`commander`对象的`args`属性(`program.args`),`program.args`是一个数组。
>Tips:
链式写法
由于`version(),option()`方法的返回值还是`Commander对象`,所以,可以采用链式写法,即可以用"."来连接各个方法,就是上面例子的写法`program.version(...).option(...)`
在命令行运行上面的例子:
~~~
$ node index -p
~~~
打印结果为
~~~
$ you ordered a pizza with:
- peppers
- marble cheese
~~~
你在命令行运行 `node index --peppers`也会打印出相同的结果。
>Tips :
这里的`index`等价于`index.js`,例子存放文件的路径,只是把.js后缀省略了
>Note:
短标志可以作为单独的参数传递。像` -abc `等于` -a -b -c`。多词组成的选项,像“`--template-engine`”会变成` program.templateEngine` 等。
#### 强制多态(option()方法的使用)
**`option()`方法的定义**
* option(flags, description, fn, defaultValue)
* `flags <String>` : 自定义参数,格式为`"-shortFlag, --longFlag null|<value>|[value]|<value>..<value>"`
* -shortFlag:”-“后面跟的是自定义参数的短标志,一般用longFlag的第一个字母(区分大小写)
* --longFlag :“--”后面跟的是自定义参数的长标志,shortFlag和longFlag必须同时存在,
* `null|<value>|[value]`:有3种情况
* `null`——不用输入这一部分的内容,即只写前面两部分,这里为了说明,才写的null,该自定义参数将在程序中被解析为布尔值`(true|false)`
* `<value>`——“<>”修饰,用户在使用-shortFlag(或--longFlag)时,强制后面带一个值,如果不输入该值,命令行程序会中断。该自定义参数将在程序中被解析为字符串(String)
* `[value]`——”[]“修饰,用户在使用-shortFlag(或--longFlag)时,允许后面带一个值,用户不输入使用默认值或undefined。
* `description <String>` : 对flags参数的描述
* `fn <Function|Mixed>` : 自定义处理参数的方法,如果传入的不是一个方法,会先判断是否为一个正则表达式,如果不是,则视为defaultValue(默认值),
* `defaultValue <Mixed>` :自定义参数默认值
* `返回值 <Object>`:commander对象
例子:
~~~
//index.js
var program = require('commander');
function range(val) {
return val.split('..').map(Number);
}
function list(val) {
return val.split(',');
}
/**
* 处理collect参数的方法,
* 在命令行调用多次调用 -c [value],memo.push将多次被调用
* @param {String} val - 用户在命令行传入的值
* @param {Array} memo - 自定义collect参数时,设置的默认值
* @return {Array} - 处理后的数组
*/
function collect(val, memo) {
memo.push(val);
return memo;
}
function increaseVerbosity(v, total) {
return total + 1;
}
program
.version('0.0.1')
.usage('[options] <file ...>')
.option('-i, --integer <n>', 'An integer argument', parseInt)
.option('-f, --float <n>', 'A float argument', parseFloat)
//限定输入格式为 <a>..<b>
.option('-r, --range <a>..<b>', 'A range', range)
.option('-l, --list <items>', '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 increased', 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 index -c 11 -c 22` 并回车运行,将发生下面的事情
1.启动一个`NodeJs`进程被启动并执行了`index.js`文件
2.`option()`方法逐个被执行,其参数被保存到`commander`对象的`options`属性中,每个`option()`方法的参数就是一个option对象,每个`option`对象都有一个`name`属性,其值等于长标志
3.parse()方法解析输入参数` node index -c 11 -c 22`
4.第一个-c参数与`name=collect`的option对象匹配,执行`collect(11,[])`,返回数组`[11]`,这时,默认值就变为了`[11]`;
5.第二个-c参数与第一个-c匹配的是同一个对象,执行`collect(22,[11])`,返回数组`[11,22]`;
6.打印出各种信息
~~~
int: undefined
float: undefined
optional: undefined
range: undefined..undefined
list: undefined
collect: ["11","22"]
verbosity: undefined
args: []
~~~
当你在命令行输入其他参数时,解析过程是一样的,可自行尝试。
#### 正则表达式(option方法的使用)
~~~
//index.js
var program = require('commander');
program
.version('0.0.1')
.option('-s --size <size>', 'Pizza size', /^(large|medium|small)$/i, 'medium')
.option('-d --drink [drink]', 'Drink', /^(coke|pepsi|izze)$/i)
.option('-f --fruit [fruit]', 'Fruit', /^(apple|banana|pear)$/i,'apple')
.parse(process.argv);
console.log(' size: %j', program.size);
console.log(' drink: %j', program.drink);
console.log(' fruit: %j', program.fruit);
~~~
>`size`的值要和正则表达式匹配,否则使用默认值
`drink`的值要和正则表达式匹配,不匹配时返回`true`,如果不输入,由于`drink`后面的值是可选的,不输入任何值时,返回`undefined`。
`fruit`的值要和正则表达式匹配,否则使用默认值,由于`fruit`后面的值是可选的,所以,不输入该值的时候,也是返回默认值。
#### command()、description()和action()
`command()`方法有点复杂,这里我们只介绍和`action()`联合起来用的最常用的方法:
~~~
var program = require('commander');
program
.version('0.0.1')
.option('-C, --chdir <path>', 'change the working directory')
.option('-c, --config <path>', 'set config path. defaults to ./deploy.conf')
.option('-T, --no-tests', 'ignore test hook')
program
.command('setup')//定义命令
.description('run remote setup commands')//对命令参数的描述信息
.action(function() {//setup命令触发时调用
console.log('setup');
});
program
.command('exec <cmd>')//定义命令,参数可以用 <> 或 [] 修饰
.description('run the given remote command')
//exec触发时调用,参数和定义的参数顺序一致
.action(function(cmd) {
console.log('exec "%s"', cmd);
});
program
.command('init')//定义命令,参数可以用 <> 或 [] 修饰
.description('run the given remote command')
.option('-y, --yes', 'without prompt')
//options是init命令的参数对象、是action回调方法的最后一个参数
.action(function(options) {
console.log('init param "%s"',options.yes );
});
program.parse(process.argv);
~~~
#### 可变参数
一个命令的最后一个参数可以是可变参数, 并且只能是最后一个参数。为了使参数可变,你需要在参数名后面追加` ... `。 下面是个示例:
~~~
var program = require('commander');
program
.version('0.0.1')
.command('rmdir <dir> [otherDirs...]')
.action(function(dir, otherDirs) {
console.log('rmdir %s', dir);
if (otherDirs) {
//otherDirs是一个数组
otherDirs.forEach(function(oDir) {
console.log('rmdir %s', oDir);
});
}
});
program.parse(process.argv);
~~~
#### 打印帮助信息
* 自动打印帮助信息
默认情况下,`commander`会根据`option()`方法的参数为你帮你实现`--help`参数,当用户在命令行使用`-h`或`--help`参数时,将自动打印出帮助信息。下面我们以**参数解析**中的例子为例:
~~~
$ node index --help
~~~
打印结果如下:
~~~
$ Usage: index [options]
Options:
-h, --help output usage information
-V, --version output the version number
-p, --peppers Add peppers
-P, --pineapple Add pineapple
-b, --bbq-sauce Add bbq sauce
-c, --cheese [type] Add the specified type of cheese [marble]
~~~
自定义帮助信息
~~~
var program = require('commander');
program
.version('0.0.1')
.option('-f, --foo', 'enable some foo')
.option('-b, --bar', 'enable some bar')
.option('-B, --baz', 'enable some baz');
// must be before .parse() since
// node's emit() is immediate
program.on('--help', function(){//自定义帮助信息
console.log(' Examples:');
console.log('');
console.log(' $ custom-help --help');
console.log(' $ custom-help -h');
console.log('');
});
program.parse(process.argv);
console.log('stuff');
~~~
主动打印帮助信息
有两种情况:
1.打印帮助信息,然后等待用户继续输入信息,不结束当前进程——`program.outputHelp()`
2.打印帮助信息,并立即结束当前进程——`program.help()`
`.outputHelp()`方法和`.help()`方法都可以带一个参数——一个回调方法,打印信息在显示到命令行之前会先传入这个方法,你可以在方法里面做必要的信息处理,比如改变文本颜色。
### commander API地址
**`commander`官网地址:https://www.npmjs.com/package/commander**