命令行工具开发(二)命令和选项解析

239 阅读7分钟

Hello 大家好,这个是系列教程命令行工具开发的第二篇文章命令行工具开发命令和选项解析

上文说了命令行工具开发之入门,今天说下命令行工具开发中的命令和选项解析

“前文传送门,命令行工具开发之入门(一)

命令行工具开发命令和选项解析

在命令行工具中,我们经常需要处理多个命令,比如 npm installnpm runnpm test 等。这些命令都是 npm 的子命令,我们可以通过 npm 命令来执行不同的操作。

1. 准备工作

创建项目

mkdir helloworld-cli
cd helloworld-cli
npm init -y

创建入口文件

cd helloworld-cli
touch index.js

index.js 文件内容

#!/usr/bin/env node
console.log('Hello, World!')

目录结构

helloworld-cli
├── index.js
└── package.json

在 package.json 中添加可执行文件

package.json中添加一个bin字段,用于指定可执行文件的位置,比如:

{
  "name": "helloworld-cli",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "bin": {
    "helloworld-cli": "index.js"
  }
}

将命令行工具添加到全局

cd helloworld-cli
npm link

不知道上面这些操作是在做什么?去看看前文呗,传送门 >> 命令行工具开发之入门(一)

2. 命令和选项解析

2.1 增加多个命令

假设,我们希望我们的命令行工具包含以下命令:

  • help 列出所有可用的命令及其命令的描述
  • random 生成一个随机数
  • time 输出当前时间
  • hello 输出 "Hello, World!"

那么,我们就必须要先了解以下两个事情:

1、一般我们在命令行工具时,我们一般是怎么使用命令的

比如,我们使用 npm 命令时,我们一般会这样使用:

npm install

上面的 install 就是 npm 的一个命令,我们通过 npm install 来安装依赖包。

2、我们的命令行工具是如何处理命令的。

Node.js 中,我们可以使用 process.argv 来获取命令行参数。process.argv 是一个数组,其中包含了命令行参数。第一个元素是 Node.js 的可执行文件路径,第二个元素是当前执行的 JavaScript 文件的路径,后面的元素则是我们传递给命令行工具的参数。

抽象不?那我们实际看一下process.argv的输出来结合上面的文字来看一下

index.js中添加以下代码:

#!/usr/bin/env node
console.log(process.argv)

然后我们运行一下:

helloworld-cli time

process_env.png

输出:

[ '/usr/local/bin/node', '/usr/local/bin/helloworld-cli', 'time' ]

可以看到,process.argv 返回了一个数组,其中包含了我们传递给命令行工具的参数。 '/usr/local/bin/node'Node.js 的可执行文件路径 '/usr/local/bin/helloworld-cli' 第二个元素是当前执行的 JavaScript 文件的路径, 'time'就是我们传递给命令行工具的命令

2.2 解析命令并针对命令执行不同的操作

现在,我们已经知道如何获取命令行参数了,接下来我们需要解析这些参数,并根据参数来执行不同的操作。

#!/usr/bin/env node
// 截取命令行参数,从第三个参数开始,因为前两个参数是 Node.js 的可执行文件路径和当前执行的 JavaScript 文件的路径
let [commandName] = process.argv.slice(2)
if (commandName === 'help') {
    let commands = [
        {
            name: 'hello',
            desc: '显示帮助信息'
        },
        {
            name: 'random',
            desc: '生成一个随机数'
        },
        {
            name: 'time',
            desc: '输出当前时间'
        },
        {
            name: 'hello',
            desc: '输出 "Hello, World!"'
        },
    ]
    commands.forEach(command => {
        console.log(`${command.name} - ${command.desc}`)
    })
    console.log()
} else if (commandName === 'hello') {
    console.log('hello world')
} else if (commandName === 'random') {
    console.log(Math.floor(Math.random() * 10))
} else if (commandName === 'time') {
    console.log(new Date().toLocaleString())
} else {
    sayHello()
}

function sayHello () {
    // 彩虹屁数组
    const caiHongPi = [
        '天涯何处无芳草,你是鲜花不是草。',
        '我做事十拿九稳,现在只差你一吻。',
        '月亮很亮,亮也没用,没用也亮,我喜欢你,喜欢也没用,没用也喜欢',
        '谁的童话书又没合好让公主跑出来了',
        `你笑起来真好看,像春天的花一样`,
        '为什么我脸皮那么厚还是包不住对你的喜欢,一不小心就露馅了',
        '你的眼里有星星✨我的眼里都是你',
        '如果你是五彩的糖,那我就当保护你小小的糖纸',
        '想和你说,今天的云和你,都十分可爱',
        '我真的好喜欢你啊 第一句话是假的 第二句也是。对方申请做您心尖尖上的宝贝,接受请求吗?'
    ]
    // 生成随机数
    const randomIndex = Math.floor(Math.random() * 10);
    // 随机输出彩虹屁
    console.log(caiHongPi[randomIndex]);
}

allComds.png

2.3 给命令增加选项

现在,我们的命令行工具已经可以处理多个命令了,但是每个命令只能执行某个特定的功能(time只能输出固定的字符串、 hello只能输出hello world)。

如果我们有一个查询天气的命令weather,支持用户传入city来指定要查询的城市呢?这就是选项(option)的作用了。

我们怎么获取到命令选项呢?

在命令行中,选项通常以 --- 开头,后面跟着选项的名称和值。例如,-c--city 表示城市选项,-d--date 表示日期选项。

helloworld-cli weather -c 北京

上面的命令中,-c 是选项的名称,北京 是选项的值。

那么,我们怎么解析这些选项呢?我们先看看process.argv 的输出:

#!/usr/bin/env node
console.log(process.argv.slice(2))

运行上面的代码,我们得到以下输出:

[ 'weather', '-c', '北京' ]

既然我们知道了选项的格式,那么我们就可以使用 process.argv 来解析选项了。

#!/usr/bin/env node
// 截取命令行参数,从第三个参数开始,因为前两个参数是 Node.js 的可执行文件路径和当前执行的 JavaScript 文件的路径
let [commandName, option, optionValue] = process.argv.slice(2)
if (commandName === 'weather') {
    if (option === '-c') {
        if (optionValue === '北京') {
            console.log('当前城市:北京, 天气:晴')
        } else if (optionValue === '上海') {
            console.log('当前城市:上海, 天气:多云')
        } else {
            console.log('当前城市:未知, 天气:未知')
        }
    } else {
        console.log('请使用 “ -c [城市名] ” 选项来指定要查询的城市,比如:-c 北京')
    }
} else if (commandName === 'help') {
    let commands = [
        {
            name: 'hello',
            desc: '显示帮助信息'
        },
        {
            name: 'random',
            desc: '生成一个随机数'
        },
        {
            name: 'time',
            desc: '输出当前时间'
        },
        {
            name: 'hello',
            desc: '输出 "Hello, World!"'
        },
    ]
    commands.forEach(command => {
        console.log(`${command.name} - ${command.desc}`)
    })
    console.log()
} else if (commandName === 'hello') {
    console.log('hello world')
} else if (commandName === 'random') {
    console.log(Math.floor(Math.random() * 10))
} else if (commandName === 'time') {
    console.log(new Date().toLocaleString())
} else {
    sayHello()
}

function sayHello () {
    // 彩虹屁数组
    const caiHongPi = [
        '天涯何处无芳草,你是鲜花不是草。',
        '我做事十拿九稳,现在只差你一吻。',
        '月亮很亮,亮也没用,没用也亮,我喜欢你,喜欢也没用,没用也喜欢',
        '谁的童话书又没合好让公主跑出来了',
        `你笑起来真好看,像春天的花一样`,
        '为什么我脸皮那么厚还是包不住对你的喜欢,一不小心就露馅了',
        '你的眼里有星星✨我的眼里都是你',
        '如果你是五彩的糖,那我就当保护你小小的糖纸',
        '想和你说,今天的云和你,都十分可爱',
        '我真的好喜欢你啊 第一句话是假的 第二句也是。对方申请做您心尖尖上的宝贝,接受请求吗?'
    ]
    // 生成随机数
    const randomIndex = Math.floor(Math.random() * 10);
    // 随机输出彩虹屁
    console.log(caiHongPi[randomIndex]);
}

weather.png

几个命令解析还好,但是每次都要写那么多的 if 判断,好麻烦啊,有没有什么办法可以简化一下呢?命令的选项和参数解析起来好麻烦,有没有什么库可以帮我们简化一下呢?

当然有,那就是 commander了。欲知后事如何,请看下回分解~ 大家可以先自己尝试一下,看看能不能用 commander 来简化一下代码。

讲完收工~

总结一下:本篇我们开发了一个helloworld-cli命令行工具,并且学习了命令行工具 是如何通过 process.argv 解析命令和参数的,也通过解析命令和参数给helloworld-cli工具开发了hellorandomtimeweatherhelp命令,并且给weather命令增加了-c选项,用来指定查询的城市。