## 命令行參數處理
在使用其他命令行程序的過程中(如`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**