前言

在开发 CLI(Command Line Interface)工具的业务场景下,离不开命令行参数的收集和解析。

接下来,本文介绍如何收集和解析命令行参数。

收集命令行参数

在 Node.js 中,可以通过 process.argv 属性收集进程被启动时传入的命令行参数:

 // ./example/demo.js
 process.argv.slice(2);

 // 命令行执行如下命令
 node ./example/demo.js --name=xiaoming --age=20 man

 // 得到的结果
 [ '--name=xiaoming', '--age=20', 'man' ]

由上述示例可以发现,Node.js 在处理命令行参数时,只是简单地通过空格来分割字符串。

对于这样的参数数组,无法很方便地获取到每个参数对应的值,所以需要再进行一次解析操作。

命令行参数风格

在解析命令行参数之前,需要了解一些常见的命令行参数风格:

  • Unix 风格:参数以「-」(连字符)开头
  • GNU 风格:参数以「--」(双连字符)开头
  • BSD 风格:参数以空格分割

Unix 参数风格有一个特殊的注意事项:「「-」后面紧邻的每一个字母都表示一个参数名」。

ls -al

上述命令用来显示当前目录下所有的文件、文件夹并且显示它们的详细信息,等同于:

ls -a -l

GNU 风格的参数以 「--」开头,一般后面会跟上一个单词或者短语,例如熟悉的 npm 安装依赖的命令:

 npm install --save koa

对于两个单词的情况,在 GNU 参数风格中,会通过「-」来连接,例如 npm 安装仅用于开发环境的依赖:

npm install --save-dev webpack

BSD 是加州大学伯克利分校开发的一个 Unix 版本。其与 Unix 的区别主要在于参数前面没有 「-」,个人感觉这样很难区别参数和参数值。

注意事项:-- 后面紧邻空格时,表示后面的字符串不需要解析。

解析命令行参数

function parse(args = []) {
 // _ 属性用来保留不需要处理的参数字符串
 const output = { _: [] };

 for (let index = 0; index < args.length; index  ) {
  const arg = args[index];
  
  if (isIgnoreFollowingParameters(output, args, index, arg)) {
   break;
  }
  
  if (!isParameter(arg)) {
   output._.push(arg);
   continue;
  }

  ...
 }

 return output;
}

parse(process.argv.slice(2));

接收到命令行参数数组之后,需要遍历数组,处理每一个参数字符串。

isIgnoreFollowingParameters 方法主要用来判断单个「--」的场景,后续的参数字符串不再需要处理:

function isIgnoreFollowingParameters(output, args, index, arg) {
 if (arg !== '--') {
  return false;
 }
 output._ = output._.concat(args.slice(  index));
 return true;
}

接下来,如果参数字符串不以「-」开头,同样也不需要处理,参数的形式以 Unix 和 GNU 风格为主:

function isParameter(arg) {
 return arg.startsWith('-');
}

参数的表现形式主要分为以下几种:

  • "--name=xiaoming": 参数名为 name,参数值为 xiaoming
  • "-abc=10": 参数名为 a,参数值为 true;参数名为 b,参数值为 true;参数名为 c,参数值为 10
  • "--save-dev": 参数名为 save-dev,参数值为 true
  • "--age 20":参数名为 age,参数值为 20
 let hyphensIndex;
 for (hyphensIndex = 0; hyphensIndex < arg.length; hyphensIndex  ) {
  if (arg.charCodeAt(hyphensIndex) !== 45) {
   break;
  }
 }

 let assignmentIndex;
 for (assignmentIndex = hyphensIndex   1; assignmentIndex < arg.length; assignmentIndex  ) {
  if (arg[assignmentIndex].charCodeAt(0) === 61) {
   break;
  }
 }

利用 Unicode 码点值找出连字符和等号的下标值,从而根据下标分割出参数名和参数值:

 const name = arg.substring(hyphensIndex, assignmentIndex);

 let value;
 const assignmentValue = arg.substring(  assignmentIndex);

处理参数值时,需要考虑参数赋值的四种场景:

 if (assignmentValue) {
  value = assignmentValue; // --name=xiaoming or -abc=10
 } else if (index   1 === args.length) {
  value = true; // --save-dev
 } else if ((''   args[index   1]).charCodeAt(0) !== 45) {
  value = args[  index]; // --age 20
 } else {
  value = true; // 缺省情况
 }

由于 Unix 风格中每一个字母都代表一个参数,并且「手动传递的参数值应该赋值给最后一个参数」,所以还需针对该场景进行适配:

 // 「-」or「--」
 const arr = hyphensIndex === 2 ? [name] : name;
 for (let keyIndex = 0; keyIndex < arr.length; keyIndex  ) {
  const _key = arr[keyIndex];
  const _value = keyIndex   1 < arr.length || value;
  handleKeyValue(output, _key, _value);
 }

最后针对参数的赋值操作,需要考虑到「多次赋值」的情况:

function handleKeyValue(output, key, value) {
 const oldValue = output[key];
 if (Array.isArray(oldValue)) {
  output[key] = oldValue.concat(value);
  return;
 }

 if (oldValue) {
  output[key] = [oldValue, value];
  return;
 }

 output[key] = value;
}

到此,命令行参数的解析功能就完成了,上述方法执行的效果如下:

 # 命令行执行
 node ./example/step1.js --name=xiaoming --age 20 --save-dev -abc=10 -c=20 -- --ignore

 # 解析结果
 {
  _: [ '--ignore' ],
  name: 'xiaoming',
  age: '20',
  'save-dev': true,
  a: true,
  b: true,
  c: [ '10', '20' ]
 }

别名机制

比较优秀的 CLI 工具在参数的解析上都支持参数的别名设置,例如使用 npm 安装开发环境依赖时,你可以选择这种完整的写法:

npm install --save-dev webpack

你也可以使用下面这种别名方式:

npm install -D webpack

从使用上来说 -D 和 --save-dev 是两种方式,但是从 CLI 工具的开发者来说,最终处理逻辑时只能以一个参数名为标准,所以对于一个命令行参数解析库来说,其结果需要包含所有的情况:

npm install --save-dev webpack
# 解析的结果
{ 'save-dev': true, 'D': true }

以上文的解析方法为例,需要添加额外的选项参数,加入 alias 属性来声明别名属性的对应关系:

 parse(process.argv.slice(2), {
  alias: {
   'save-dev': 'S'
  }
 })

上述方式符合正常的理解:设置参数对应的别名。但这是一个「单向查找关系」,需要转化为:

 "alias": {
  "save-dev": ["s"],
  "s": ["save-dev"]
 }

因为对于使用者来说,只会选择一种方式传递参数。对于开发者的话需要根据任意一个别名找到其相关联的别名:

function parse(args = [], options = {}) {
 const output = { _: [] };

 const { alias } = options;

 const hasAlias = alias !== void 666;

 if (hasAlias) {
  Object.keys(alias).forEach(key => {
   alias[key] = toArr(alias[key]);
   alias[key].forEach((item, index) => {
    (alias[item] = alias[key].concat(key)).splice(index, 1);
   })
  })
 }

 // 省略解析代码
 ...

 if (hasAlias) {
  Object.keys(output).forEach(key => {
   const arr = alias[key] || [];
   arr.forEach(sub => output[sub] = output[key])
  })
 }

 return output;
}

除了别名之外,还可以在参数解析之后做如下优化:

  • 参数值的类型约束
  • 参数的默认值设定

成熟的解析库

针对一些成熟的命令行参数解析库可以采用基准测试查看它们的解析效率:

const nopt = require('nopt');
const mri = require('mri');
const yargs = require('yargs-parser');
const minimist = require('minimist');
const { Suite } = require('benchmark');

const bench = new Suite();
const args = ['--name=xiaoming', '-abc', '10', '--save-dev', '--age', '20'];

bench
 .add('minimist   ', () => minimist(args))
 .add('mri     ', () => mri(args))
 .add('nopt     ', () => nopt(args))
 .add('yargs-parser ', () => yargs(args))
 .on('cycle', e => console.log(String(e.target)))
 .run();

本文的内容主要参考解析效率最高的 mri 库的源码,感兴趣的同学可以学习其源码实现。(顺便吐槽一下:嵌套三元操作符可读性真的很差。。)

虽然上述基准测试中 minimist 效率并不很好,但是其覆盖了比较全的参数输入场景。(以上测试用例覆盖的场景有限)

到此这篇关于Node.js 中如何收集和解析命令行参数的文章就介绍到这了,更多相关Node.js 解析命令行参数内容请搜索Devmax以前的文章或继续浏览下面的相关文章希望大家以后多多支持Devmax!

Node.js 中如何收集和解析命令行参数的更多相关文章

  1. osx – 你如何访问Swift中的命令行参数?

    如何访问Swift中的命令行应用程序的命令行参数?注意,我使用的范围1..C_ARGC,因为C_ARGV“数组”的第一个元素是应用程序的路径。C_ARGV变量实际上不是一个数组,但可以像数组一样进行子脚本化。

  2. CentOS 8.2服务器上安装最新版Node.js的方法

    这篇文章主要介绍了CentOS 8.2服务器上安装最新版Node.js的方法,本文给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下

  3. node.js三个步骤实现一个服务器及Express包使用

    这篇文章主要介绍了node.js三个步骤实现一个服务器及Express包使用,文章通过新建一个文件展开全文内容,具有一定的参考价值,需要的小伙伴可以参考一下

  4. Node.js调试技术总结分享

    Node.js是一个可以快速构建网络服务及应用的平台。该平台的构建是基于Chrome's JavaScript runtime,也就是说,实际上它是对Google V8引擎(应用于Google Chrome浏览器)进行了封装。 今天介绍Node.js调式目前有几种技术,需要的朋友可以参考下。

  5. node.js实现http服务器与浏览器之间的内容缓存操作示例

    这篇文章主要介绍了node.js实现http服务器与浏览器之间的内容缓存操作,结合实例形式分析了node.js http服务器与浏览器之间的内容缓存原理与具体实现技巧,需要的朋友可以参考下

  6. 教你如何使用node.js制作代理服务器

    本文介绍了如何使用node.js制作代理服务器,图文并茂,十分的详细,代码很简洁易懂,这里推荐给大家。

  7. node.js中的fs.openSync方法使用说明

    这篇文章主要介绍了node.js中的fs.openSync方法使用说明,本文介绍了fs.openSync方法说明、语法、接收参数、使用实例和实现源码,需要的朋友可以参考下

  8. Node.js+ELK日志规范的实现

    这篇文章主要介绍了Node.js+ELK日志规范的实现,小编觉得挺不错的,现在分享给大家,也给大家做个参考。一起跟随小编过来看看吧

  9. node.js爬虫框架node-crawler初体验

    这篇文章主要介绍了node.js爬虫框架node-crawler的相关资料,帮助大家利用node.js进行爬虫,感兴趣的朋友可以了解下

  10. node.js中的fs.existsSync方法使用说明

    这篇文章主要介绍了node.js中的fs.existsSync方法使用说明,本文介绍了fs.existsSync方法说明、语法、接收参数、使用实例和实现源码,需要的朋友可以参考下

随机推荐

  1. Error: Cannot find module ‘node:util‘问题解决

    控制台 安装 Vue-Cli 最后一步出现 Error: Cannot find module 'node:util' 问题解决方案1.问题C:\Windows\System32>cnpm install -g @vue/cli@4.0.3internal/modules/cjs/loader.js:638 throw err; &nbs

  2. yarn的安装和使用(全网最详细)

    一、yarn的简介:Yarn是facebook发布的一款取代npm的包管理工具。二、yarn的特点:速度超快。Yarn 缓存了每个下载过的包,所以再次使用时无需重复下载。 同时利用并行下载以最大化资源利用率,因此安装速度更快。超级安全。在执行代码之前,Yarn 会通过算法校验每个安装包的完整性。超级可靠。使用详细、简洁的锁文件格式和明确的安装算法,Yarn 能够保证在不同系统上无差异的工作。三、y

  3. 前端环境 本机可切换node多版本 问题源头是node使用的高版本

    前言投降投降 重头再来 重装环境 也就分分钟的事 偏要折腾 这下好了1天了 还没折腾出来问题的源头是node 使用的高版本 方案那就用 本机可切换多版本最终问题是因为nodejs的版本太高,导致的node-sass不兼容问题,我的node是v16.14.0的版本,项目中用了"node-sass": "^4.7.2"版本,无法匹配当前的node版本根据文章的提

  4. nodejs模块学习之connect解析

    这篇文章主要介绍了nodejs模块学习之connect解析,小编觉得挺不错的,现在分享给大家,也给大家做个参考。一起跟随小编过来看看吧

  5. nodejs npm package.json中文文档

    这篇文章主要介绍了nodejs npm package.json中文文档,本文档中描述的很多行为都受npm-config(7)的影响,需要的朋友可以参考下

  6. 详解koa2学习中使用 async 、await、promise解决异步的问题

    这篇文章主要介绍了详解koa2学习中使用 async 、await、promise解决异步的问题,小编觉得挺不错的,现在分享给大家,也给大家做个参考。一起跟随小编过来看看吧

  7. Node.js编写爬虫的基本思路及抓取百度图片的实例分享

    这篇文章主要介绍了Node.js编写爬虫的基本思路及抓取百度图片的实例分享,其中作者提到了需要特别注意GBK转码的转码问题,需要的朋友可以参考下

  8. CentOS 8.2服务器上安装最新版Node.js的方法

    这篇文章主要介绍了CentOS 8.2服务器上安装最新版Node.js的方法,本文给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下

  9. node.js三个步骤实现一个服务器及Express包使用

    这篇文章主要介绍了node.js三个步骤实现一个服务器及Express包使用,文章通过新建一个文件展开全文内容,具有一定的参考价值,需要的小伙伴可以参考一下

  10. node下使用UglifyJS压缩合并JS文件的方法

    下面小编就为大家分享一篇node下使用UglifyJS压缩合并JS文件的方法,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧

返回
顶部