node实现shell命令管理工具及commander.js学习

目录
  • 背景、
  • 一、用法演示
    • 1: 安装
    • 2: 添加
    • 3: 查看 + 使用'
    • 4: 移除
    • 5: add有变量的命令
    • 6: 使用变量
  • 二、初始化自己的node项目
  • 三、初始化命令 + 全局安装
  • 四、commander.js (node命令行解决方案)
    • 番外
  • 五、inquirer.js(node命令行交互插件)
  • 六、添加命令: add
  • 七、移除命令: rm
  • 八、查看+使用: ls
    • 1: 查看ls, 支持传参 -a
    • 2: 判断命令语句中是否有变量
    • 3: 无变量 -> 执行
    • 4: 有变量 -> 执行
  • 九、让文字变色 (chalk)

背景、

github 地址: https://github.com/lulu-up/record-shell

你有没有经历过忘记某个shell命令怎么拼写? 或是懒得打一长串命令的经历? 比如我的mac笔记本的tachbar偶尔会'卡死', 这时我就要输入 killall ControlStrip 命令重启tachbar, 你也看到了这个命令真心懒得打。

还有新建react项目我每次都要输入npx create-react-app 项目名 --template typescript, 在公司的日常开发中我习惯每次写新需求都单独clone项目并创建新的分支进行开发, 此时就需要去gitlab上复制项目地址然后在本地git clone xxxxxxxxxx 新的项目名, 理论上这些操作真的很重复。

首先本次要带你用node一起动手做一款记录shell命令的小插件, 当然网上类似插件也是有的, 但我这次做了一个最简单粗暴的版本, 自己用着也爽的版本, 并且也想趁机温习一遍命令行相关知识。

一、用法演示

先一起看看这个'库'是否真的方便:

1: 安装

npm install record-shell -g

安装完毕你的全局会多出 rs命令:

2: 添加

rs add

起名随意, 甚至全用汉语更舒服, 这里先演示输入简单命令:

3: 查看 + 使用'

rs ls

命令是可选择的, 这里我先多加几个凑所的命令用来演示:

可以按上下键移动选择, 回车即可执行命令:

当然也可以查看命令详情, 只需-a参数:

rs ls -a

4: 移除

rs rm

5: add有变量的命令

我们的命令当然不会都是写'死'的模式啦, 比如命令 echo 内容 > a.txt, 这里的意思是我要把内容写入目标文件:

6: 使用变量

使用命令时会引导我们填入变量, 所以定义时写汉语就行:

二、初始化自己的node项目

接下来一起从零开始做出这个库, 考虑到一些新手同学可能没做过这种全局的node包, 我这里就讲的详细一些。

初始化项目没啥好说的, 随便起名:

npm init

改造package.json文件:

"bin": {
    "rs": "./bin/www"
  },

这里在 bin内指明, 当运行 rs 命令的时候, 访问"./bin/www"

#! /usr/bin/env node
require('../src/index.js')
  • #! 这个符号通常在Unix系统的基本中第一行开头中出现,用于指明这个脚本文件的解释程序。
  • /usr/bin/env 因为可能大家会把node安装到不同的目录下, 这里直接告诉系统可以在PATH目录中查找, 这样就兼容了不同的node安装路径。
  • node 这个自不必说, 就是去查找咱们的node命令。

三、初始化命令 + 全局安装

这里讲一下如何将我们的命令挂在到全局, 使你可以在任何地方都能使用全局的rs命令:

// cd 我们的项目
npm install . -g

这里比较好理解吧, 相当于直接把项目安装在了全局, 我们平时install xxx -g 是去远端拉取, 这个命令是拉当前目录。

此时那你向index.js文件内写入console.log('全局执行'), 再全局执行 rs 并看到如下效果就是成功了:

四、commander.js (node命令行解决方案)

先安装再聊:

npm install commander

commander的可以帮我们非常规范的处理用户的命令, 比如用户在命令行输入rs ls -a, 原生node的情况下我可以先将输入的args进行拆解, 拆解出 ls 与 -a, 然后再写一堆if判断如果是ls并且后面有-a则如何去做, 但显然这样写不规范, 代码也难以维护, commander就是来帮我们规范这些写法的:

将下面的代码放进 index.js文件中:

const fs = require("fs");
const path = require("path");
const program = require('commander');
const packagePath = path.join(__dirname, "../package.json")
const packageData = JSON.parse(fs.readFileSync(packagePath, 'utf-8'));
program.version(packageData.version)
program
    .command('ls [-type]')
    .description('description')
    .action((value) => {
        console.log('你输入的是:', value)
    })
program.parse(process.argv)

在命令行输入:

rs ls 123456

逐句解释一下代码:

  • const program = require('commander')这里很明显引入了commander
  • program.version(packageData.version)此处是定义了当前的版本, 当你输入rs -V时会展示program.version方法获取到的值, 此处直接使用了package.json里面的version字段。
  • program.command('ls') 定义了名为ls的参数, 当我们输入rs ls时才会触发我们后面的处理方法, 我之所以写成program.command('ls [-type]')是因为加上[-type]commander才会认为ls命令后面可以跟其他参数, 当然你叫[xxxxx]也可以, 让使用者能看懂即可。

.description('description')顾名思义这里是简介描述, 当我们输入rs -h的时候会出现:

  • .action方法就是commander检测到当前命令触发时的处理函数, 第一个参数是用户传入的参数, 第二个参数是Command对象, 后续我们会在这里弹出选择列表。
  • process.argv这里要先知道processnode中的全局变量, 其中argv是启动命令行时的所有参数。
  • program.parse(process.argv)看完上面这里就好理解了, 将命令行参数传递给commander开始执行。

番外

如果你配置program.option('ls', 'ls的介绍'), 则当用户输入rs -h时会出现, 但我感觉加了有点乱, 咱们的插件追求简单所以就没加。

五、inquirer.js(node命令行交互插件)

npm install inquirer

inquirer可以帮我们生成各种命令行问答功能, 就像vue-cli差不多的效果, 大家可以输入下面代码试一试'单选模式':

program
    .command('ls [-type]')
    .description('description')
    .action(async (value) => {
        const answer = await inquirer.prompt([{
            name: "key",
            type: "rawlist",
            message: "message1",
            choices: [
                {
                    name: 'name1',
                    value: 'value1'
                },
                {
                    name: 'name2',
                    value: 'value2'
                }
            ]
        }])
        console.log(answer)
    })

逐句解释一下代码:

  • 首先这里是一个asyncawite的模式。
  • inquirer.prompt参数是一个数组, 因为它可以连续操作, 比如进行两次单选列表操作。
  • name就是最终的key, 比如namexxxx用户选择了1, 则最终返回结果就是{xxxx:1}
  • type指定交互类型rawlist单选列表、 input输入、checkbox多选列表等。
  • message就是提示语, 我们让用户选择之前总要告诉他这里在做啥吧。
  • choices选项的数组, name选项名, value选项值。

六、添加命令: add

正式开始做第一个命令, 我新建了一个名为env的文件夹, 里面创建record-list.json文件用了存储用户的命令:

add命令无非就是往record-list.json文件里面增加内容:

program
    .command('add')
    .description('添加命令')
    .action(async () => {
        const answer = await inquirer.prompt([{
            name: "name",
            type: "input",
            message: "命令名称:",
            validate: ((name) => {
                if (name !== '') return true
            })
        }, {
            name: "command",
            type: "input",
            message: "命令语句, 可采用[var]的形式传入变量:",
            validate: ((command) => {
                if (command !== '') return true
            })
        }])
          let shellList = getShellList();
          shellList = shellList.filter((item) => item.name !== answer.name);
          shellList.push({
             "name": answer.name,
             "command": answer.command
          })
          fs.writeFileSync(dataPath, JSON.stringify(shellList));
    })

逐句解释一下代码:

  • 首先我们使用commander定义了add命令;
  • 当触发add命令时我们使用inquirer定义了两个输入框, 第一个输入命令名称, 第二个输入命令语句。
  • validate定义了对入参的校验, 注意: 用户不输入值不是undefined而是空字符串, 所以使用了 !== '', 如果校验不通过无法继续操作。
  • 用户填写完毕就向record-list.json添加数据, 同时如果是重名的命令就进行替换。

名称可能会重复, 但是不要紧, 因为它的使用场景决定了它不需要做过多的限制。

七、移除命令: rm

这里的原理就是拉取record-list.json数据进行删减, 然后更新record-list.json:

program
    .command('rm')
    .description('移除命令')
    .action(async () => {
        let shellList = getShellList();
        const choices = shellList.map((item) => ({
            key: item.name,
            name: item.name,
            value: item.name,
        }));
        const answer = await inquirer.prompt([{
            name: "names",
            type: "checkbox",
            message: `请'选择'要删除的记录`,
            choices,
            validate: ((_choices) => {
                if (_choices.length) return true
            })
        }])
        shellList = shellList.filter((item) => {
            return !answer.names.includes(item.name)
        })
        fs.writeFileSync(dataPath, JSON.stringify(shellList));
    })

逐句解释一下代码:

  • choices是定义了一组可选项。
  • 使用checkbox多选模式, 让用户可以一次删除多个命令。
  • validate校验了什么都不删的情况, 因为可能使用户忘了点击选取(空格键)。
  • 使用filter过滤掉名称相同的命令。
  • 最后更新record-list.json文件。

八、查看+使用: ls

这里内容稍微多一点, 毕竟一个命令负责两个能力, 这里的核心原理是拉取record-list.json文件的内容展示成单选列表, 然后根据用户选取的值进行命令的执行, 最后返回执行结果;

1: 查看ls, 支持传参 -a

program
    .command('ls')
    .alias('l')
    .description('命令列表')
    .option('-a detailed')
    .action(async (_, options) => {
        const shellList = getShellList();
        const choices = shellList.map(item => ({
            key: item.name,
            name: `${item.name}${options.detailed ? ': ' + item.command : ''}`,
            value: item.command
        }));
        if (choices.length === 0) {
            console.log(`
            您当前没有录入命令, 可使用'rs add' 进行添加
            `)
            return
        }
        const answer = await inquirer.prompt([{
            name: "key",
            type: "rawlist",
            message: "选择要执行的命令",
            choices
        }])
    })

逐句解释一下代码:

  • option('-a detailed')定义了可以接收-a参数, 比如ls -a, 并且如果用户传了-a则会得到返回值{detailed: true}
  • 如果有-a则将命令本身放在name属性里展示出来。
  • choices是转换了record-list.json文件里的数据的列表数据。
  • 如果record-list.json数据是空的, 则提示用户去使用rs add进行添加。
  • 使用inquirer生成单选列表。

2: 判断命令语句中是否有变量

由于允许用户输入的命令内带变量, 比如前面演示过的 echo [内容] > [文件名], 那我就要判断当前用户选中的命令内是否有变量:

const optionsReg = /\[.*?\]/g;
function getShellOptions(command) {
    const arr = command.match(optionsReg) || [];
    if (arr.length) {
        return arr.map((message) => ({
            name: message,
            type: "input",
            message,
        }));
    } else {
        return []
    }
}

逐句解释一下代码:

  • optionsReg正则匹配出所有 '[这种写法]'的变量。
  • 如果匹配到了变量则返回一个数组, 这个数组的长度是变量的个数, 因为每个变量都要有一次输入的机会。
  • 没有对重复的name进行特殊处理, 并且name会变成返回值的key, 所以不可以重名, 重名的话回会导致只处理第一个变量。

3: 无变量 -> 执行

这里有一个新的概念:

const child_process = require('child_process');

child_process可以生成node的'子进程', child_process.exec方法是启动了一个系统shell来解析参数,因此可以是非常复杂的命令,包括管道和重定向。

child_process.exec(command, function (error, stdout) {
        console.log(`${stdout}`)
        if (error !== null) {
            console.log('error: ' + error);
        }
    });

逐句解释一下代码:

  • command是要执行的命令。
  • stdout执行命令的输出, 比如ls就是输出当前目录中的文件信息。
  • error这里也很重要, 如果报错了要让用户知道报错信息, 所以也console了。

4: 有变量 -> 执行

核心原理是解析'变量'后对命令语句进行替换, 然后正常执行就ok:

function answerOptions2Command(command, answerMap) {
    for (let key in answerMap) {
        command = command.replace(`[${key}]`, answerMap[key])
    }
    return command;
}
function handleExec(command) {
    child_process.exec(command, function (error, stdout) {
        console.log(`${stdout}`)
        if (error !== null) {
            console.log('error: ' + error);
        }
    });
}
 if (shellOptions.length) {
        const answerMap = await inquirer.prompt(shellOptions)
        const command = answerOptions2Command(answer.key, answerMap)
        handleExec(command)
    } else {
        handleExec(answer.key)
    }

逐句解释一下代码:

  • inquirer执行完会返回一个字典, 比如{[文本]:"xxxxx", [文件名]:"a.txt"}, 因为我们设置了namemessage使用同样的名称。
  • answerOptions2Command循环执行replace进行变量的替换。
  • handleExec负责执行语句。

九、让文字变色 (chalk)

功能都完成了, 但是我们的提示文字还是'黑白的', 我们当然希望命令行中多姿多彩一些, 在node中使用:

var red = "\033[31m red \033[0m";
console.log('你好红色:', red)

\033c语言中的转义字符这里就不扩了, 反正看到他就是要对屏幕进行操作了, 但是我们可以看出上面的写法很不友好, 肯定要封装一下下, chalk.js就是个不错的已有轮子, 我们下进行安装:

npm install chalk

使用:

const chalk = require('chalk')
chalk.red('你好: 红色')

你高兴太早了, 现在是有问题的 !!

其他教程里都没说怎么解决, 其实那你只要把chalk的版本降低到4就ok了!

以上就是node实现shell命令管理工具及commander.js学习的详细内容,更多关于node shell命令管理的资料请关注我们其它相关文章!

(0)

相关推荐

  • commander脚手架工具使用详解

    目录 概述 下载 预备工作 核心重要的选项 option command 总结 概述 在当前我们前端项目中,大多数情况下,都是使用对应框架开发的脚手架进行项目工程化的搭建,既然要用到脚手架,那么肯定会用到命令,比如vue-cli的创建命令:vue create projectName,要想解析控制台输出的自定义命令,离不开commander这个工具. commander负责将参数解析为选项和命令参数.记录一期这个工具的基本使用. 下载 npm install commander 预备工作 第一步

  • Vue脚手架搭建及创建Vue项目流程的详细教程

    目录 VUE脚手架搭建流程 安装国内淘宝镜像 安装 Vue 脚手架 Vue项目创建 项目结构解读 项目修改测试 总结 VUE脚手架搭建流程 1.安装 Node.js(推荐一个网站:http://nodejs.cn/) 2.下载安装完成之后进行测试,记住安装位置 node -v 测试 node 是否安装成功以及检查 node 版本 npm -v 测试 npm 是否安装成功以及检查 npm 版本 效果: 提示:如果碰到下面情况 可能原因: 可能是C:\Users\Administrator(user

  • VUE脚手架框架编写简洁的登录界面的实现

    目录 前言 安装Vue脚手架 创建登录界面的文件--login.Vue 配置路由-- router.js 配置main.js 编写登录界面--Login.Vue 总结 前言 一个好的前端开发项目,都是一个团队负责一个部分进行通力合作的.简单的一个系统网站一般包含登录.主体.各个模块功能这三个大部分,现在我们写的登录界面,我们一般编写这样的登录界面可以说有一定的固定套路. 安装Vue脚手架 1.我们一Windows 10为例子,安装Vue 3脚手架前,我们先要配置node js环境: 2.接下来,

  • 从零搭建SpringBoot+MyBatisPlus快速开发脚手架

    目录 前言 聊聊mall-tiny项目 项目简介 项目演示 技术选型 数据库表结构 接口文档 使用流程 升级过程 Swagger升级 Spring Security升级 MyBatis-Plus升级 解决循环依赖问题 解决跨域问题 总结 前言 关注我Github的小伙伴应该了解,之前我开源了一款快速开发脚手架mall-tiny,该脚手架继承了mall项目的技术栈,拥有完整的权限管理功能.最近抽空把该项目支持了Spring Boot 2.7.0,今天再和大家聊聊这个脚手架,同时聊聊升级项目到Spr

  • nodejs命令行参数处理模块commander使用实例

    诚然,之前处理都是使用内置的process.agrv ,这个能work,但是不好使,于是tj大神给写了一个,my god,完全的高大上: 1.安装 复制代码 代码如下: npm install commander 2.option 解析 Options with commander are defined with the .option() method, also serving as documentation for the options. The example below pars

  • jenkins中如何集成commander应用的完整步骤

    前言 Jenkins 是一款流行的开源持续集成(Continuous Integration)工具,广泛用于项目开发,具有自动化构建.测试和部署等功能. 最近参加公司的集成测试平台的开发,在开发中遇到了不少问题,两个星期的迭代也即将完成,在这也用这篇博客记录下开发中的问题,供读者参考 公司的应用较多,所以需要了解这几种应用在jenkins中如何做构建,我自己参与的有两种commander的应用,一种是大数据类的,一个是我们服务端架构组的scala应用 1.大数据应用BigData 配置如下: 配

  • node实现shell命令管理工具及commander.js学习

    目录 背景. 一.用法演示 1: 安装 2: 添加 3: 查看 + 使用' 4: 移除 5: add有变量的命令 6: 使用变量 二.初始化自己的node项目 三.初始化命令 + 全局安装 四.commander.js (node命令行解决方案) 番外 五.inquirer.js(node命令行交互插件) 六.添加命令: add 七.移除命令: rm 八.查看+使用: ls 1: 查看ls, 支持传参 -a 2: 判断命令语句中是否有变量 3: 无变量 -> 执行 4: 有变量 -> 执行 九

  • SQLite教程(八):命令行工具介绍

    工欲善其事,必先利其器.学好SQLite的命令行工具,对于我们学习SQLite本身而言是非常非常有帮助的.最基本的一条就是,它让我们学习SQLite的过程更加轻松愉快.言归正传吧,在SQLite的官方下载网站,提供了支持多个平台的命令行工具,使用该工具我们可以完成大多数常用的SQLite操作,就像sqlplus之于Oracle.以下列表给出了该工具的内置命令: 命令名 命令说明 .help 列出所有内置命令. .backup DBNAME FILE 备份指定的数据库到指定的文件,缺省为当前连接的

  • node命令行工具之实现项目工程自动初始化的标准流程

    一.目的 传统的前端项目初始流程一般是这样: 可以看出,传统的初始化步骤,花费的时间并不少.而且,人工操作的情况下,总有改漏的情况出现.这个缺点有时很致命. 甚至有马大哈,没有更新项目仓库地址,导致提交代码到旧仓库,这就很尴尬了... 基于这些情况,编写命令行工具(CLI)的目的就很明确: 用于新项目工程的初始化利用工具进行初始化,可以节省项目初期的准备时间避免出现改漏的情况杜绝未更新项目版本仓库地址的问题 以下是新的流程示意图: 二.自动化流程分析 以下是自动化流程图: 从流程图可以得出两个重

  • 浅谈node.js 命令行工具(cli)

    一. 先了解一下package.json 每个项目的根目录都有一个 package.json 文件,定义了这个项目所需要的各种模块,以及项目的配置信息,下面是一个比较完整的package.json文件 { "name": "vue-cli", "version": "2.9.3", "description": "A simple CLI for scaffolding Vue.js projec

  • 详解Node.js如何开发命令行工具

    前言 Node 给前端开发带来了很大的改变,促进了前端开发的自动化,我们可以简化开发工作,然后利用各种工具包生成生产环境.如运行sass src/sass/main.scss dist/css/main.css即可编译 Sass 文件. 在实际的开发过程中,我们可能会有自己的特定需求, 那么我们得学会如何创建一个Node命令行工具. hello world 老规矩第一个程序为hello world.在工程中新建bin目录,在该目录下创建名为helper的文件,具体内容如下: #!/usr/bin

  • 利用node.js制作命令行工具方法教程(一)

    前言 之前使用过一些全局安装的NPM包,安装完之后,可以通过其提供的命令,完成一些任务.比如Fis3,可以通过fis3 server start 开启fis的静态文件服务,通过fis3 release开启文件编译与发布:还有vue-cli,可以通过vue init webpack my-project来初始化vue+webpack的项目基础配置.最近有一个需求,需要写一个类似vue-cli的NPM包,通过命令行操作实现项目初始配置,所以就查看了相关资料,学习了一下如何使用node来生成自己的命令

  • IIS7 全新管理工具AppCmd.exe的命令使用实例分享

    IIS 7 提供了一个新的命令行工具 Appcmd.exe,可以使用该工具来配置和查询 Web 服务器上的对象,并以文本或 XML 格式返回输出. 下面是一些可以使用 Appcmd.exe 完成的任务的示例: •创建和配置站点.应用程序.应用程序池和虚拟目录. •停止和启动站点. •启动.停止和回收应用程序池. •查看有关在 Web 服务器上运行的工作进程和请求的信息. Appcmd.exe 为常见的查询和配置任务提供了一致的命令,从而降低了学习语法的复杂性. 例如,您可以使用 list 命令来

  • 浅谈node模块与npm包管理工具

    在Node.js中,以模块为单位划分所有的功能,并且提供了一个完整的模块加载机制,所以我们可以将应用程序划分为各个不同的部分,并且对这些部分进行很好的协同管理.通过将各种可重用代码编写在各种模块中的方法,可以大大减少应用程序的代码量,提高应用程序的开发效率以及应用程序代码的可读性.通过模块加载机制,可以将各种第三方模块引入到我们的应用程序中. 在node.js中,提供npm包管理工具,用于从第三方网站上下载各种Node.js包. 一.模块 1.1 加载模块 在Node.js中,以模块为单位划分所

  • node进程管理工具PM2用法详解

    PM2是node进程管理工具,可以利用它来简化很多node应用管理的繁琐任务,如性能监控.自动重启.负载均衡等,而且使用非常简单. 安装 npm install -g pm2 常用命令 $ npm install pm2 -g # 命令行安装 pm2 $ pm2 start app.js -i 4 #后台运行pm2,启动4个app.js # 也可以把'max' 参数传递给 start # 正确的进程数目依赖于Cpu的核心数目 $ pm2 start app.js --name my-api #

  • Node.js中的package.json与cnpm命令行工具介绍

    一.包 Nodejs 中除了它自己提供的核心模块外,我们可以自定义模块,也可以使用第三方的模块.Nodejs 中第三方模块由包组成,可以通过包来对一组具有相互依赖关系的模块进行统一管理. 完全符合 CommonJs 规范的包目录一般包含如下这些文件. package.json :包描述文件. bin :用于存放可执行二进制文件的目录. lib :用于存放 JavaScript 代码的目录. doc :用于存放文档的目录. 在 NodeJs 中通过 NPM 命令来下载第三方的模块(包). http

随机推荐