如何使用nodejs开发交互式命令行程序

Posted by WWJ Blog on December 18, 2019

如何使用Nodejs开发交互式命令行程序

命令行程序,也就是通过文本在终端中与程序进行交互 比如我们常常使用的create-react-app命令,会生成一个最基本的react项目;执行webpack命令会给项目进行打包处理,执行npm install XXX 会安装package; 为什么只要在终端输入一行字符就可以实现这么强大的功能呢?Nodejs命令行到底是如何工作的?

解释器

在终端可以调用不同的解释器来执行你的文件,比如运用sh解释器来执行一个.sh文件,我们都知道执行npm init之后会在项目的根目录下生成一个.git文件夹,.git文件夹下的hooks目录则是一系列的.sh文件,这一系列的sh文件则会在git各生命周期被触发,我们随意改写一个文件,并运用sh解释器来执行它

给pre-commit.sh新增一行echo语句

#!/bin/sh
echo 'hello wuwenjie'

if git rev-parse --verify HEAD >/dev/null 2>&1
then
	against=HEAD
else
	# Initial commit: diff against an empty tree object
	against=$(git hash-object -t tree /dev/null)
fi

在终端执行sh pre-commit.sh 则会在终端输出 hello wuwenjie Alt text

node则是用来执行js文件的解释器,当使用node来执行一个js文件的时候,程序将会去系统的环境变量中寻找node命令,找到之后便使用系统中的node命令执行文件。 新建一个hello-world.js的文件,并用node指令来执行它

// hello-world.js
console.log('hello world');
$ node hello-world.js
hello world

这样做还是比较麻烦,因为每次都得输入node hello-world.js才可以执行你的文件,有没有什么方法可以在省略node的情况下程序依然可以被执行,比如在终端输入./hello-world.js,也可以打印出hello world

可执行脚本

如果想实现在终端输入./hello-world.js,也可以打印出hello world,那么方法就是在文件的第一行加上#!/usr/bin/env node, 该行代码由两部分组成:即 Shebang 和 解释器命令。Shebang 就是开头的#!,它告诉系统调用后面声明的解释器,而我们需要调用的解释器是node。通过/usr/bin/env node可以寻找到系统环境变量中的node命令。env的作用是为了解决不同系统中node命令存放的地方不一致的问题,可以通过env动态查询。

./hello-world.js文件的第一行加上#!/usr/bin/env node 之后,我们再来看看终端输出的是不是我们所预期的hello world

加上#!/usr/bin/env node 之后,我们可以通过省略node来执行文件,但是这其实还是很不方便的,因为每次都要进入文件的系统目录下执行命令,有什么方法可以让程序的可执行文件全局可用,应该很容易想到一种方法,就是将其加入到系统 的环境变量中,然而另外一种更加简便的方法则是通过软链接的方式。

软链接

软链接是Linux系统下的名词,在Linux中,链接分为两种,即硬链接和软链接。

硬连接指的是一块数据有不同的名字,在读写的时候本质上是访问的同一块数据。在Linux系统中,多个文件名指向同一块数据是被允许的,其作用可以防止文件被误删。硬链接在删除的时候只是删除了一个名字,当所有的名字都被删除之后,数据才会消失。

软链接有点类似于Windows系统中的快捷方式,是一种特殊文本文件。它其实是包含了指向源文件的路径,独立于源文件存在。创建了一个文件的软链接之后,执行这个文件的软链,接,实际上会进入源文件的系统路径,执行源文件。也有点类似于C语言的指针。 删除软链接之后源文件不会受到影响,但是删除源文件之后,软链接就是一个空壳。

执行命令ln -s [源文件或目录] [目标文件或目录],为 [源文件或目录]创建软链接,将其链到[目标文件或目录];

我们在 /Users/wuwenjie2001/Desktop目录下创建一个hello-world.js文件,并写入以下代码

#!/usr/bin/env node
console.log('hello world ');

在终端执行ln -s ~/Desktop/hello-world.js /usr/local/bin/hello-world 再执行hello-world Alt text

到现在已经达到了我们想要的效果,即在系统中的任何地方打开终端,输入hello-world,控制台都会输出 hello world; 但还是有一点不好的地方就是,通过ln -s创建软链接的时候,源文件和目标文件的路径都必须是绝对路径,虽然可以通过pwd可以获取文件的绝对路径,但依然还是有点繁琐的。

那么有什么方法可以解决这个问题呢?

npm link命令可以将一个任意位置的文件链接到全局执行环境,从而在任意位置使用命令行都可以直接运行该文件 简单的讲,npm link 做了两件事情(以linux系统为例)

  • 为可执行文件创建软链接,将其链接到{prefix}/bin/{name}
  • 再创建之后的软链接所在目录创建软链接,将其链接到{prefix}/lib/node_modules/{package}

不过要使npm link起作用的话,必须先使用npm init 初始化项目; 我们新建一个项目,并用npm init 初始化它

$ mkdir hello-world
cd hello-world
npm init -y

执行之后,目录中生成了package.json文件,用编辑器打开它,其内容为:

{
  "name": "hello-world",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "keywords": [],
  "author": "",
  "license": "ISC"
}

在package.json添加

"bin": {
	"hello-world": "bin/index.js"
}

,表示npm会在全局环境创建一个名为hello-world的软链接,软链接的源文件为该项目下的bin/index.js,在bin/index.js文件中输入以下内容,并执行npm link

#!/usr/bin/env node
console.log('hello world ');

此时,在任意一个位置执行以下命令都可以看到输出hello world 总结一下,npm link命令通过链接目录和可执行文件,实现npm包命令的全局可执行。

到这一步,我们已经可以写出一个简单的命令行程序了

命令行参数

在网页应用里,我们依靠 URL 来获得展现页面内容所需要的参数,而命令行程序所依靠的便是命令行参数。以hello-world为例,假设我们需要在控制台打印n次hello world,而n是用户输入的。比如我们想输出3个hello world,我们会可能这么做hello-world 3,那么如何在命令行程序里拿到这个4呢?

我们都知道,有一个系统变量process.argv,该变量的第二个值就是命令行参数,改写bin/index.js文件

#!/usr/bin/env node
console.log(process.argv);
const times = process.argv[2] || 1;
console.log('hello world '.repeat(times));

然后在终端可以看到期望的结果: Alt text process.argv 是实际执行的命令参数列表(数组),第一个参数是解释器命令 node所在路径,第二个是被执行文件的软链接所在路径,第三个参数则是命令行参数,也正是我们需要的。

之后你便可以灵活地通过 argv 来判断如何输出用户期望的内容了。

这篇文章的标题是如何使用Nodejs开发交互式命令行程序,除去交互式功能,我们已经可以使用Nodejs开发命令行程序了,那么如何给命令行程序添加交互式功能呢?

inquire模块

inquirer努力打造一个易于嵌入和外形美观的node命令行界面,它提供了用户界面和查询会话流程,借助inquirer这个模块就能轻松实现命令行的交互式功能,它的语法如下(来自官方网站

npm install inquirer
var inquirer = require('inquirer');
inquirer
  .prompt([
    /* Pass your questions in here */
  ])
  .then(answers => {
    // Use user feedback for... whatever!!
  });

inquirer功能简介

  • input–输入
  • validate–验证
  • list–列表选项
  • confirm–提示
  • checkbox–复选框等等

这些功能具体要怎么实现可以移步官方网站

我们改写一下bin/index.js文件,看看列表选项功能具体要怎么实现

#!/usr/bin/env node

const inquirer = require('inquirer');
const prompt = inquirer.createPromptModule();

// 选择需要在哪个项目中创建模版文件
const choiceProject = () => {
  return new Promise((resolve) =>{
    prompt({
      type: 'list',
      message: '请选择项目',
      name: 'projectName',
      choices: [
        "xiaomai-miniapp-show",
        "xiaomai-web-b-new",
        "xiaomai-web-parents",
        "xiaomai-template-template",
      ]
    }).then((answer) => {
      console.log(answer.projectName)
    });
  });
};

choiceProject();

在终端执行命令hello-world,可以看到 Alt text

了解到inquirer模块之后,我们可以使用Nodejs开发交互式命令行程序了。最后,本文内容也就到此结束了,希望这篇文章可以帮助到你!