本文参考了Commander.js中文文档、跟着老司机玩转Node命令行、inquirer.js —— 一个用户与命令行交互的工具
Commander.js
提供了用户命令行输入和参数解析的强大功能,帮助我们简化命令行开发,通过 主指令
+ 子指令
+ 参数
的模式运行命令,具有以下特性:
- 参数解析
- 强制多态
- 可变参数
- Git 风格的子命令
- 自动化帮助信息
- 自定义帮助等
准备工作
安装 node.js
安装 commander.js,执行 npm install commander --save
demo
/// 任意目录新建任意文件,比如 index.js
const program = require("commander");
program
.version("1.0.0")
.parse(process.argv);
在控制台中执行 node index.js -V
,就可以看到版本信息的输出
API
命令 | 说明 |
---|---|
.version | 显示版本命令,默认选项标识为 -V 和 --version ,当存在时会打印版本号并退出 |
.parse 和 .parseAsync | 用于解析 process.argv ,设置 options 以及触发 commands (解析命令行) |
.option | 定义命令的选项 |
.requiredOption | 设置选项为必填,必填选项要么设有默认值 |
.command 和 .addCommand | 添加命令名称 |
.alias | 定义命令的别名 |
.usage | 定义命令的用法 |
.description | 定义命令的描述 |
.action | 定义命令的回调函数 |
.help(cb) | 输出帮助信息并立即退出 |
.outputHelp(cb) | 输出帮助信息的同时不退出 |
.helpInformation() | 获取除了--help 以外的帮助信息 |
.helpOption(flags, description) | 重写覆盖默认的帮助标识和描述 |
program.helpOption('-e, --HELP', 'read more information') |
|
.addHelpCommand() | 打开或关闭(.addHelpCommand(false) )隐式的帮助命令 |
version
除使用默认的参数,还可以自定义标识,通过给 version方法再传递一个参数,语法与option方法一致
program.version('0.0.1', '-v, --vers', 'output the current version')
.parse 和 .parseAsync
.parse
的第一个参数是要解析的字符串组,你可以省略参数以隐式使用 process.argv
program.parse(process.argv); // 显式的 node 约定
program.parse(); // 隐式的,自动监测的 electron
process.argv
属性会返回一个数组,其中包含当 Node.js 进程被启动时传入的命令行参数。 第一个元素是 process.execPath(启动 Node.js 进程的可执行文件的绝对路径名 )。 第二个元素是正被执行的 JavaScript 文件的路径。 其余的元素是任何额外的命令行参数
如果参数遵循与 node 不同的约定,你可以在第二个参数中传递 from 选项:
program.parse(['-f', 'filename'], { from: 'user' });
·option
使用 .option()
方法定义 commander 的选项 options
,每个选项可以有一个短标识(单个字符)和一个长名字,它们之间用逗号或空格或 '|' 分开。
.option('-n, --name<path>(自定义标)', 'name description(选项描述)', 'default name(默认值)')
option
接收三个参数:
自定义标志<必须>
:分为长短标识,中间用逗号、竖线或者空格分割;标志后面可跟必须参数或可选参数,前者用<>
包含,后者用[]
包含选项描述<非必须>
:在使用 --help 命令时显示标志描述默认值<非必须>
- 多词选项如 "--template-engine" 会被转为驼峰法
program.templateEngine
- 多个短标识可以组合为一个破折号开头的参数:布尔标识和值,并且最后一个标识可以附带一个值。 例如,
-a -b -p 80
也可以写作-ab -p80
甚至-abp80
最常用的两个选项类型是 boolean(选项后面不跟值) 和 选项跟一个值(使用尖括号声明),除非在命令行中指定,否则两者都是 undefined
const { program } = require("commander");
program
.version("1.0.0")
.option("-d, --debug", "output extra debugging")
.option("-s, --small", "small pizza size")
.option("-p, --pizza-type <type>", "flavour of pizza")
.option("-c, --cheese <type>", "add the specified type of cheese", "blue")
.parse(process.argv);
if (program.debug) console.log(program.opts());
console.log("pizza details:");
if (program.small) console.log("- small pizza size");
if (program.pizzaType) console.log(`- ${program.pizzaType}`);
- node index.js --help 自动化帮助信息 help
/// 控制台输出
Usage: index [options]
Options:
-V, --version output the version number
-d, --debug output extra debugging
-s, --small small pizza size
-p, --pizza-type <type> flavour of pizza
-c, --cheese <type> add the specified type of cheese (default: "blue")
-h, --help display help for command
还可以通过监听 --help 来显示额外信息,在调用完成后触发
.on("--help", () => {
console.log("");
console.log("Example call(使用例子):");
console.log(" $ custom-help --help");
}) // 要在 parse 之前使用
.parse(process.argv);
...
/// 控制台比上面回多输出
Example call(使用例子):
$ custom-help --help
此时 usage 的信息是系统自动生成的,可以通过设置 .usage
和 .name
设置个性化帮助信息说明
.name("my-command")
.usage("[global options] command")
.parse(process.argv);
...
/// 此时控制台输出
Usage: my-command [global options] command // 之前为Usage: index [options]
Options:
- node index.js -d
/// 控制台返回
/// program.opts 展示的就是所有 option 的设置值
/// 此处的运行命令是 -d 就是启用选项,但是选项后面不跟值,所以对应的值为true,其他未设定为 undefined
{ version: '1.0.0',
debug: true,
small: undefined,
pizzaType: undefined } // 多词选项转为驼峰
pizza details:
cheese: blue
- node index.js -p
/// 控制台返回
error: option '-p, --pizza-type <type>' argument missing
/// 因为 p 后的参数是必要参数
- node index.js -p vegetarian
/// 控制台返回
pizza details:
- vegetarian
cheese: blue
...
/// 此处等价于下面的写法
/// node index.js --pizza-type=vegetarian
- node index.js -ds
/// 控制台返回
/// 多个短标示组合为一个参数 -ds
{ version: '1.0.0',
debug: true,
small: true,
pizzaType: undefined }
pizza details:
- small pizza size
cheese: blue
- 选项默认值
上面都会有个输出
cheese: blue
这个 blue 是 -c 时对应的默认值
/// 虽然有默认值,如果要去设置相关option时,还是要传递一个值(此时时必传)
node index.js -c // error: option '-c, --cheese <type>' argument missing
- 使用
--
来指示选项的结束,任何剩余的参数会正常使用,而不会被命令解释
/// 按定义,如果不使用 -- 会提醒差参数,使用后就直接结束,不进行后面的解释
node index.js -- p
- 以
--no
前缀开头的多词选项是其后选项的布尔值的反。 例如,--no-sauce
将program.sauce
的值设置为false
const { program } = require('commander');
program
.version('1.0.0')
.option('-n --no-sauce', 'Remove sauce')
.parse(process.argv);
if (program.sauce) console.log("sauce");
...
/// 单独定义,选项默认值为 true
node index.js // { version: '1.0.0', sauce: true }
...
node index.js -n // { version: '1.0.0', sauce: false }
如果先定义 --foo
,再加上 --no-foo
并不会改变它本来的默认值,需要自行定义
program
.version("1.0.0")
// .option("-d, --debug", "output extra debugging")
// .option("-s, --small", "small pizza size")
// .option("-p, --pizza-type <type>", "flavour of pizza")
// .option("-c, --cheese <type>", "add the specified type of cheese", "blue")
.option("-n --no-sauce", "Remove sauce")
.option("-f --foo", "test foo")
.option("-nf --no-foo", "test no foo")
.parse(process.argv);
if (program.debug) console.log(program.opts());
console.log(program.opts());
...
/// 同时定义了 --foo、--no-foo 就有默认值
node index.js // { version: '1.0.0', sauce: true, foo: undefined }
...
/// 需要自行处理
node index.js -nf // { version: '1.0.0', sauce: true, foo: false }
自定义选项处理
option 还可以自定义处理函数,其接收两个参数:用户传入的值、上一个值(previous value),它会返回新的选项值(可以将选项值强制转换为所需类型,或累积值,或完全自定义处理),在函数后面指定选项被当作默认或初始值
自定义函数适用场景包括:参数类型转换、参数暂存或者其他自定义处理的场景
const { program } = require("commander");
const setFoo = (value, previousValue) => {
// 传入的 value 默认会是 string 类型
return Number(value) + 1;
};
program
.version("1.0.0")
.option("-f --foo <number>", "set foo info", setFoo) // 第三个参数接收这个函数
.parse(process.argv);
console.log(program.opts());
...
node index.js -f 21; // { version: '1.0.0', foo: 22 } 接收的值为处理后的值
在函数后面指定值
const { program } = require("commander");
const setFoo = (value, previousValue) => {
console.log("value", value);
console.log("proValue", previousValue);
return Number(value) + 1;
};
program
.version("1.0.0")
.option("-F --Foo <number>", "set foo info", setFoo, 20)
.parse(process.argv);
console.log(program.opts());
...
node index.js -F 10;
...
value 10 // 函数接收用户的传入值
proValue 20 // 20 是设定的默认值
{ version: '1.0.0', Foo: 11 }
必需选项
你可以使用 .requiredOption
指定一个必需(强制性)选项,可以指定或者给定一个默认值
const { program } = require("commander");
const setFoo = (value, previousValue) => {
console.log("value", value);
console.log("proValue", previousValue);
return Number(value) + 1;
};
program
.version("1.0.0")
.requiredOption("-F --Foo <number>", "set foo info", setFoo, 20)
.parse(process.argv);
console.log(program.opts());
...
node index.js; // { version: '1.0.0', Foo: 20 } 20是默认值
/// 若删除默认值会报 error: required option '-F --Foo <number>' not specified
command/addCommand
定义命令行指令
.command('rmdir <dir> [otherDirs...](命令名称)', 'install description(命令描述)', opts(配置选项))
参数解析:
- 命令名称<必须>:命令后面可跟用
<>
或[]
包含的参数;命令的最后一个参数可以是可变的,像实例中那样在数组后面加入...
标志;在命令后面传入的参数会被传入到action
的回调函数以及program.args
数组中 - 命令描述<可省略>:如果存在,且没有显示调用action(fn),就会启动子命令程序,否则会报错
- 配置选项<可省略>:可配置
noHelp
、isDefault
等
const { program } = require("commander");
program
.command("clone <source> [destination]")
.description("clone a repository into a newly created directory") // description 可以不在 command 中填写
.action((source, destination) => {
console.log(source); // 这里的 source 是从 command 中接收的
console.log("clone command called");
});
program.parse(process.argv);
console.log(program.args);
...
node index.js clone /bash;
/// 控制台输出
/bash // action 中的 source
clone command called
[ 'clone', '/bash' ] // 这是 program.args 输出
指定参数语法
可以通过使用 .arguments
来为最顶级命令(the top-level command)指定参数,并且对于子命令来说参数都在 .command
的对应回调中。
尖括号 <>
意味着必须的输入,而方括号 []
则是代表了可选的输入
const program = require("commander");
program
.version("0.1.0")
.arguments("<cmd> [env]")
.action(function(cmd, env) {
cmdValue = cmd;
envValue = env;
});
program.parse(process.argv);
if (typeof cmdValue === "undefined") {
console.error("no command given!");
process.exit(1);
}
console.log("command:", cmdValue);
console.log("environment:", envValue || "no environment given");
...
node index.js cmd env // 对应顶级命令参数
/// 终端输出
command: cmd
environment: env
一个命令有且仅有最后一个参数是可变的,通过类似剩余参数的写法进行调用
const { program } = require("commander");
program
.version("0.1.0")
.command("rmdir <dir> [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 index.js rmdir foo info
/// 终端输出
rmdir foo
rmdir info
子命令的 action handler
可以为一个子命令的 option 添加 action handler,这个 回调函数,接收两个参数。 第一个是声明的参数的变量,第二个命令对象自己。
const program = require("commander");
program
.command("rm <dir>")
.option("-r, --recursive", "Remove recursively")
.action(function(dir, cmdObj) {
console.log("remove " + dir + (cmdObj.recursive ? " recursively" : ""));
});
program.parse(process.argv);
...
node index.js rm obj -r
/// 终端输出
remove obj recursively
自定义事件监听
通过 program.on()
监听相关 option 或 command
const program = require("commander");
program.version("0.0.1").option("-l --list", "show list");
program.on("option:list", function() {
console.log("option list call");
});
program.parse(process.argv);
...
node index.js -l
/// 终端输出
option list call
inquirer.js
在开发的过程中,我们需要频繁的跟命令行进行交互,借助 inquirer 这个模块就能轻松实现,它提供了用户界面和查询会话流程(就是那种问答式交互)
安装
npm i -S inquirer
基本用法
const inquirer = require('inquirer');
const promptList = [
// 具体交互内容 (问题列表)
];
inquirer.prompt(promptList).then(answers => { // 返回结果 })
.catch(error => {
if(error.isTtyError) {
// 无法在当前环境中呈现提示
} else {
// 别的错误
}
});
具体交互内容的格式如下:
{
type: "input",
message: "设置一个用户名:",
name: "name",
default: "test_user" // 默认值
}
下面是一个完整的例子
const inquirer = require("inquirer");
const promptList = [
{
type: "input",
message: "设置一个用户名:",
name: "name",
default: "test_user" // 默认值
}
];
inquirer.prompt(promptList).then(answer => {
console.log(answer);
});
API
参数 | 类型 | 描述 |
---|---|---|
type | String |
表示提问的类型 |
包括input(默认) , number , confirm , list , rawlist , expand , checkbox , password , editor |
||
name | String |
存储当前问题回答的变量 |
message | ``String | Function`` |
default | ``String | Number |
choices | ``Array | Function`` |
validate | Function |
对用户的回答进行校验 |
filter | Function |
对用户的回答进行过滤处理,返回处理后的值 |
transformer | Function |
对用户回答的显示效果进行处理(如:修改回答的字体或背景颜色),但不会影响最终的答案的内容; |
when | Function, Boolean |
根据前面问题的回答,判断当前问题是否需要被回答 |
pageSize | Number |
修改某些type类型下的渲染行数 |
prefix | String |
修改message默认前缀 |
suffix | String |
修改message默认后缀 |
askAnswered | Boolean |
如果答案已经存在,则强制提示该问题 |
loop | Boolean |
启用列表循环(默认值 true) |
常见例子
input
const promptList = [
{
type: "input",
message: "设置一个用户名:",
name: "name",
default: "test_user" // 默认值
},
{
type: "input",
message: "请输入手机号:",
name: "phone",
validate: function(val) { // validate的使用例子
if (/^1[3456789]\d{9}$/.test(val)) {
// 校验手机号是否正确
return true;
}
return "请输入正确的手机号";
}
}
];
number
const promptList = [
{
type: "number",
message: "你的手机号:",
name: "phone"
}
];
输入的非数字会被转为 NaN,这个参数有点 input 语法糖的意思。input 和 validate 能实现一样的功能
confirm
const promptList = [
{
type: "confirm",
message: "是否使用监听?",
name: "watch",
prefix: "前缀"
},
{
type: "confirm",
message: "是否进行文件过滤?",
name: "filter",
suffix: "后缀",
when: function(answers) { // 上一个问题答案为true时展示该问题(when的用法)
return answers.watch;
}
}
];
list
const promptList = [
{
type: "list",
message: "请选择一种水果:",
name: "fruit",
choices: ["Apple", "Pear", "Banana"],
filter: function(val) { // filter 的用法
// 使用filter将回答变为小写
return val.toLowerCase();
}
}
];
rawlist
与 list
一样,都是列表展示,这个会显示数字
const promptList = [
{
type: "rawlist",
message: "请选择一种水果:",
name: "fruit",
choices: ["Apple", "Pear", "Banana"]
}
];
expand
指定命令的联想输入
const promptList = [
{
type: "expand",
message: "请选择一种水果:",
name: "fruit",
choices: [
{
key: "a",
name: "Apple",
value: "apple"
},
{
key: "O",
name: "Orange",
value: "orange"
},
{
key: "p",
name: "Pear",
value: "pear"
}
]
}
];
只能输入 a
、O
、P
这些设定好的参数,如果故意输错,按确定系统还好把这些问题转为 rawlist
供选择
checkbox
const promptList = [
{
type: "checkbox",
message: "选择颜色:",
name: "color",
choices: [
{
name: "red"
},
new inquirer.Separator(), // 添加分隔符
{
name: "blur",
checked: true // 默认选中
},
{
name: "green"
},
new inquirer.Separator("--- 分隔符 ---"), // 自定义分隔符
{
name: "yellow"
}
]
}
];
运行时会有这么一个提示:Press <space> to select, <a> to toggle all, <i> to invert selection(空格 选择,a 全选,i 反选)
/// 还有另外一种格式
const promptList = [
{
type: "checkbox",
message: "选择颜色:",
name: "color",
choices: ["red", "blur", "green", "yellow"],
pageSize: 2 // 设置行数
}
];
这种模式下,可以固定设置展示几行,可以通过上下的方向键查看选项,选项是会循环,如果不想循环可以设置 loop: false
停用
password
const promptList = [
{
type: "password", // 密码为密文输入
message: "请输入密码:",
name: "pwd"
}
];
editor
const promptList = [
{
type: "editor",
message: "请输入备注:",
name: "editor"
}
];
运行后会进入到一个 vim 的编辑器
插件
除了上面提到的 type 类型,inquirer.js
提供了可以自定义的插件方法 inquirer.registerPrompt(name, prompt)
(上面使用的方法是 inquirer.prompt(questions) -> promise
),社区已经有了些比较优秀的插件
inquirer-table-prompt
这是一个 table 表格插件,针对那种大量选择的场景比较有效(其他插件到 inquirer 的 github 主页去查看)
const inquirer = require("inquirer");
// 要先安装 inquirer-table-prompt
inquirer.registerPrompt("table", require("inquirer-table-prompt"));
const promptList = [
{
type: "table", // registerPrompt 的第一个参数,第二个参数是引入相关库
name: "workoutPlan",
message: "Choose your workout plan for next week",
columns: [
{
name: "Arms",
value: "arms"
},
{
name: "Legs",
value: "legs"
},
{
name: "Cardio",
value: "cardio"
},
{
name: "None",
value: undefined
}
],
rows: [
{
name: "Monday",
value: 0
},
{
name: "Tuesday",
value: 1
},
{
name: "Wednesday",
value: 2
},
{
name: "Thursday",
value: 3
},
{
name: "Friday",
value: 4
},
{
name: "Saturday",
value: 5
},
{
name: "Sunday",
value: 6
}
]
}
];
inquirer.prompt(promptList).then(answer => {
console.log(answer);
});
chalk.js
一个美化插件,node终端样式库,没有什么特别好介绍的,具体颜色和API参看 官方文档
可以利用这个库,把我们写出的交互命令适当的添加些颜色,方便查看区分
const chalk = require("chalk");
const promptList = [
{
type: "number",
message: "你的手机号:",
name: "phone",
validate: function(val) {
// validate的使用例子
if (/^1[3456789]\d{9}$/.test(val)) {
// 校验手机号是否正确
return true;
}
return chalk.red("请输入正确的手机号"); // 把需要变色的部分包裹起来即可
}
}
];