{T}

命令行参数解析

前面写的 node 脚本都是编译完直接跑的:

而实际上用的 Node.js 命令行工具一般都支持传入参数:

那这些命令行参数在 node 代码里怎么解析呢?

来学两个包: minimist、commander。

分别来试一下:

bash
mkdir cli-args-test
cd cli-args-test
npm init -y

写下 index.js

javascript
console.log(process.argv)

跑一下:

ini
node ./index.js -x 3 -y 4 -n5 -abc --xxx=8 aaa bbb ccc

可以看到, node 会分割所有的命令行参数,放到 process.argv 里。

但一般用不到前两个参数,都是取后面的来解析:

javascript
process.argv.slice(2)

先用 minimist 来解析下:

安装 minimist

css
npm install --save minimist

改下 index.js

javascript
const minimist = require("minimist")

const argv = minimist(process.argv.slice(2))
console.log(argv)

跑一下:

可以看到,所有的选项都被解析成了对象的属性,而参数放在了 _ 属性的数组里。

一般把前面这种 -xx=11 叫做选项,后面的叫做参数(也叫位置参数)。

有的同学可能会疑惑,为啥 -abc 解析成 a: true、b:true、c: true 了呢?

这就涉及到命令行参数的规范了。

简单过一下:

首先,参数的位置并不固定,可以放在选项中间、选项之前。

bash
node ./index.js  bbb -x 3 ccc -y 4  aaa
node ./index.js  bbb ccc -y 4 -x 3 aaa
node ./index.js  -y 4 -x 3 aaa bbb ccc

其次,布尔类型的短选项可以合并:

bash
node ./index.js aaa  -y -x
node ./index.js aaa  -xy

但长选项不会合并:

bash
node ./index.js aaa -xy

一般一个选项会提供短选项和长选项:

选项和选项值之间可以用等号也可以用空格隔开:

ini
node ./index.js  -x=1 -y 2 aaa

如果有的选项你想作为参数,可以加上 --,-- 之后的所有内容都会作为参数

ini
node ./index.js  -x=1 -- -y 2 aaa

大概就这些规范。

平时敲的命令都是符合这个规范的。

minimist 等命令行解析程序都是按照这个来解析的。

minimist 还支持传入一些 options

index2.js

javascript
const minimist = require("minimist")

const argv = minimist(process.argv.slice(2), {
  boolean: ["x"],
  string: ["y"]
})
console.log(argv)

选项就有两种 string 和 boolean,可以在 boolean 和 string 选项显示声明。

声明后会按照对应的类型来解析参数:

bash
node ./index2.js -x 1 -y

可以看到,x 虽然传入的选项值是 1 但被当做 boolean 解析为了 true。

而 -y 被解析为了 string。

没声明的选项默认也会解析:

当然,你也可以指定哪些解析哪些不解析

javascript
const minimist = require("minimist")

const argv = minimist(process.argv.slice(2), {
  boolean: ["x"],
  string: ["y"],
  unknown(arg) {
    return arg === "-u"
  }
})
console.log(argv)
bash
node ./index2.js -x 1 -y -z dong
node ./index2.js -x 1 -y -u dong

你还可以指定默认值:

javascript
const minimist = require("minimist")

const argv = minimist(process.argv.slice(2), {
  boolean: ["x"],
  string: ["y"],
  unknown(arg) {
    return arg === "-u"
  },
  default: { y: 2333 }
})
console.log(argv)

指定别名:

javascript
const minimist = require("minimist")

const argv = minimist(process.argv.slice(2), {
  boolean: ["x"],
  string: ["y"],
  unknown(arg) {
    return arg === "-u"
  },
  default: { y: 2333 },
  alias: { p: "port", t: "template" }
})
console.log(argv)
bash
node ./index2.js  -p 8080

这就是 minimist 的全部功能了。

虽然简单,但用的非常多

一天就有 5000 多 w 下载量

每天都是:

(我用的时候周平均下载量没出来,但是看每天下载量也一样)

每天用的 create-vite 也是用的 minimist:

但是 minimist 的功能比较简单,只是解析参数,不能打印 help 信息、不支持解析命令自动执行对应函数,一切都要自己来做。

比如 help 信息在 create-vite 里就是自己实现的打印:

也就是这个:

当然,那是因为 create-vite 比较简单,只有一个 -t 参数。

那如果参数多了呢?

比如 @nestjs/cli,它支持这些子命令:

bash
nest @nestjs/cli -h

比如 nest new、nest start、nest build

然后子命令也有一些选项:

自己实现帮助信息的打印能累死。

而且还有命令解析后的执行、子命令解析后的执行。

这时候用 minimist 来做就不合适了。

会用 commander 来做。

@nestjs/cli 就是用的 commander:

试一下:

css
npm install --save commander

index3.js

javascript
const { Command } = require("commander")
const program = new Command()

program.name("string-util").description("一些字符串工具的 CLI").version("0.8.0")

program
  .command("split")
  .description("分割字符串为字符数组")
  .argument("<string>", "分割的字符串")
  .option("--first", "只展示第一个子串")
  .option("-s, --separator <char>", "分割字符", ",")
  .action((str, options) => {
    const limit = options.first ? 1 : undefined
    console.log(str.split(options.separator, limit))
  })

program.parse()

跑一下:

bash
node index3.js

可以看到,会自动打印生成的 help 信息。

有一个子命令 split,看下帮助信息:

bash
node ./index3.js split -h

有两个选项 --first 是只展示第一个字符、--sperator 是指定分隔符。

试一下:

bash
node ./index3.js split a,b,c
node ./index3.js split abc -s ''
node ./index3.js split abc -s '' --first

其实这是一个功能挺全面的命令行工具了。

有 help 信息、有子命令,命令和子命令都有选项。

要是用 minimist 来做,这些都要自己实现。

但是用 commander,这些都是内置的。

回过头来看下刚才的代码:

commander 是链式调用的风格,description 传入命令描述、argument 是参数、option 是选项。

如果选项有别名、选项值,都是在 option 第一个参数里声明:

尖括号代表必填、方括号代表可选。

但选项值这里一般都是用尖括号。

选项和子命令是可选的,用方括号展示:

参数值、选项值,一般都用尖括号展示:

这不仅仅是展示用,如果你没带尖括号,就不能正常解析:

当你用了尖括号,但没有传选项值的时候,就会报错:

这种都不用自己处理,比 minimist 方便多了。

所以 commander 的下载量也比 minimist 多:

每天稳定在 1.5 亿的下载量。

但这两种库都有各自的使用场景:

minimist 只解析命令行参数,不做其他的事情,如果比较简单的命令行工具,比如只有一个 --template 选项的 create-vite,就可以用 minimist。

commander 内置了 help 打印、自动执行命令对应的 action、子命令 等功能,比 minimist 完善很多,大的命令行工具可以用 commander,比如 @nestjs/cli 就是用的 commander。

当然,还有其他的命令行解析工具,比如 yargs

但还是 commander 用的更多,也更完善。

基本上,小的命令行工具用 minimist、大的命令行工具用 commander。

这俩二选一就行。

代码上传了文档仓库

总结

这节学了命令行解析工具 minimist 和 commander。

node 会把命令行参数传入 process.argv 数组。

自己解析比较麻烦,会用一些库来解析。

minimist 能解析命令行的选项、参数,可以设置默认值等。但是功能比较简单。

commander 还能做 help 打印、子命令、自动执行命令对应的 action 等功能。

基本上,小的命令行工具用 minimist,大的命令行工具用 commander 就可以了。