Node.js 中如何收集和解析命令行参数_node.js

来源:脚本之家  责任编辑:小易  

前言

在开发 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 解析命令行参数内容请搜索真格学网以前的文章或继续浏览下面的相关文章希望大家以后多多支持真格学网!

您可能感兴趣的文章:node.js使用yargs处理命令行参数操作示例node.js命令行教程图文详解Node.JS在命令行中检查Chrome浏览器是否安装并打开指定网址Node.js命令行/批处理中如何更改Linux用户密码浅析浅谈node.js 命令行工具(cli)详解用Node.js写一个简单的命令行工具利用node.js制作命令行工具方法教程(一)Node.js 使用命令行工具检查更新详解Node.js 命令行程序开发教程Node.js利用Net模块实现多人命令行聊天室的方法详解Node.js如何开发命令行工具

  • 本文相关:
  • node.js中.pfx后缀文件的处理方法
  • node.js中的console.time方法使用说明
  • 浅谈node 调试工具入门教程
  • 详解axios在node.js中的post使用
  • node.js之readline模块的使用详解
  • 用node写一条配置环境的指令
  • zabbix添加node.js监控的方法
  • 了不起的node.js读书笔记之node的学习总结
  • 零基础之node.js搭建api服务器的详解
  • 详解用node.js实现restful风格webservice
  • nodejs执行js文件和命令行下输入的区别
  • nodejs 如何从命令行指定文件路径 和 输出路径?
  • 如何用命令行开启nodejs搭建web服务器?
  • 如何用用命令行开启nodejs搭建web服务器
  • java怎么调用node.js并传参?比如怎么调用下面图片...
  • nodejs 怎么自定义全局对象
  • nodejs中如何获取cmd的输入,有Readline方法吗,在...
  • 如何基于nodejs开发一个命令行工具
  • nodejs命令行怎么复制文件内容
  • 怎么在命令行中查看nodejs和npm的版本号?
  • 网站首页网页制作脚本下载服务器操作系统网站运营平面设计媒体动画电脑基础硬件教程网络安全基础知识javascript类库表单特效广告代码网页特效黑客性质javascript技巧domnode.jsjs其它首页javascriptnode.js使用yargs处理命令行参数操作示例node.js命令行教程图文详解node.js在命令行中检查chrome浏览器是否安装并打开指定网址node.js命令行/批处理中如何更改linux用户密码浅析浅谈node.js 命令行工具(cli)详解用node.js写一个简单的命令行工具利用node.js制作命令行工具方法教程(一)node.js 使用命令行工具检查更新详解node.js 命令行程序开发教程node.js利用net模块实现多人命令行聊天室的方法详解node.js如何开发命令行工具node.js中.pfx后缀文件的处理方法node.js中的console.time方法使用说明浅谈node 调试工具入门教程详解axios在node.js中的post使用node.js之readline模块的使用详解用node写一条配置环境的指令zabbix添加node.js监控的方法了不起的node.js读书笔记之node的学习总结零基础之node.js搭建api服务器的详解详解用node.js实现restful风格webservice一行命令搞定node.js 版本升级卸载安装node.js与npm过程详解yarn的安装与使用详细介绍node.js(安装,启动,测试)nodejs中调用系统命令、shell脚本node.js+ajax实现获取http服务器node.js中的fs.writefilesync方法nodejs npm install全局安装和本nodejs基本语法和类型nodejs学习笔记之fs文件模块详解从nodejs搭建中间层再谈前后端分离如何让nodejs支持h5 history模式(connectexpress + node.js实现登录拦截器的实例代node实现基于token的身份验证详解用node搭建简单的静态资源管理器从零开始学习node.js系列教程三:图片上传详解nodejs操作mongodb数据库封装db类nodejs 搭建简单的web服务器详解及实例node.js下when.js 的异步编程实践在linux系统中搭建node.js开发环境的简单
    免责声明 - 关于我们 - 联系我们 - 广告联系 - 友情链接 - 帮助中心 - 频道导航
    Copyright © 2017 www.zgxue.com All Rights Reserved