## 命令行参数处理 在使用其他命令行程序的过程中(如`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**