首页 > 其他分享 >终端交互脚本

终端交互脚本

时间:2024-06-24 09:43:28浏览次数:23  
标签:脚本 console name answers 终端 inquirer 交互 port log

终端交互命令行脚本

简述

基于nodejs环境编写的交互式命令行脚本,使用到的npm包主要有以下三个

  1. execa:执行脚本命令
  2. inquirer(核心包):用于在终端中进行提问与回答的交互操作
  3. detect-port:用于检测端口是否被占用

包使用详细介绍

execa

安装

npm install [email protected]

使用

import { execa } from "execa"
execa('echo', ['hello', 'world'], { cwd: "../day08-script" })
    .then(result => {
        console.log("stdout", result.stdout); // 输出日志: Hello, World!
        console.log("exitCode", result.exitCode); // 输出子进程退出状态码
        console.log("stderr", result.stderr); // 输出子进程错误信息
    })
    .catch(error => {
        console.error('执行命令失败:', error);
    });

参数

  • 命令
    • 各种命令,只要在系统上能跑的
  • 参数
    • 以字符串数组的形式自动拼接到命令后
  • 配置(最常用)
    • cwd:当前的命令执行路径,会先到该路径下后再执行命令
    • env:环境变量对象,这些变量将传递给子进程。
    • shell:是否在 shell 中执行命令。
    • stdio:子进程的标准 I/O 配置。可以是 'inherit', 'pipe', 或 'ignore'
    • timeout:命令执行的超时时间(毫秒)。

inquirer

安装

npm install inquirer

使用

import inquirer from "inquirer"

inquirer.prompt([
    {
        type: 'input', // 问题类型,这里是输入框
        name: 'username', // 问题的答案将存储在这个属性中
        message: 'What is your name?', // 显示给用户的问题
        validate: function(value) {
          if (value.length < 1) {
            return 'Name cannot be empty'; // 验证函数,确保输入不为空
          }
          return true;
        }
    },
])
.then(async (answers) => {
    console.log("选择了:", answers)
});

调用 inquirer.prompt() 并传入问题数组,然后使用 Promise 来处理用户的答案。

常用属性

type: 问题类型,例如 input, confirm, list, checkbox 等。

name: 用于存储用户答案的属性名。

message: 显示给用户的问题。

default: 默认答案,如果用户没有输入任何内容,将使用这个默认值。

choices: 选项列表,对于 list, rawlist, expand, checkbox 等类型的问题,这个属性定义了用户可以选择的选项。

validate: 一个函数,用于验证用户输入是否有效。如果返回 false 或者一个错误消息字符串,输入将被视为无效。

filter: 一个函数,用于在将用户输入存储到答案对象之前对输入进行处理或转换。

when: 一个函数,返回一个布尔值,用于决定是否显示这个问题。

pageSize: 用于 list 和 rawlist 类型的问题,定义了一次显示多少个选项。

prefix: 用于 list 和 rawlist 类型的问题,定义了选项前缀。

suffix: 用于 list 和 rawlist 类型的问题,定义了选项后缀。

transformer: 一个函数,用于修改 list, rawlist, 和 expand 类型问题中选项的显示方式。

loop: 用于 confirm 类型的问题,如果为 true,则用户必须输入 y 或 n 来继续。

source: 用于动态生成选项,是一个函数,返回一个 Promise,该 Promise 解析为选项数组。

store: 一个布尔值,如果为 true,则每个选项的值将被添加到最终的答案对象中。

choicesAlign: 用于 checkbox 类型的问题,定义了选项的对齐方式。

highlight: 用于 list 和 rawlist 类型的问题,定义了是否高亮显示当前选中的选项。

pointer: 用于 list 和 rawlist 类型的问题,定义了当前选中的选项的指针字符。

type

Inquirer.js 提供了多种问题类型(type),每种类型都具有不同的功能,以下是一些常见的问题类型及其功能:

  1. input: 标准输入框,用户可以输入任何文本。
  2. confirm: 确认框,用户可以选择 "yes" 或 "no"。
  3. list: 下拉列表,用户可以从预设的选项中选择一个。
  4. rawlist: 与 list 类似,但是选项以纯文本形式展示,不带高亮。
  5. expand: 下拉列表,带有展开/折叠选项,用户可以选择一个选项,或者展开查看更多信息。
  6. checkbox: 复选框,用户可以选择多个选项。
  7. password: 密码输入框,输入内容不会显示在屏幕上。
  8. editor: 编辑器,允许用户在文本编辑器中输入多行文本。
  9. range: 范围选择器,用户可以选择一个数值范围。
  10. autocomplete: 自动完成输入框,用户可以输入文本,并且得到自动完成的建议。

每种类型都有其特定的用途,以下是一些类型的详细功能:

  • input:

    • 允许用户输入任何文本。
    • 可以设置 validate 函数来验证输入。
    inquirer.prompt([
      {
        type: 'input',
        name: 'username',
        message: 'What is your username?',
        validate: value => {
          if (value.length) {
            return true;
          }
          return 'Please enter a username';
        }
      }
    ]).then(answers => {
      console.log('Username:', answers.username);
    });
    
  • confirm:

    • 通常用于需要用户确认的操作。
    • 默认情况下,用户可以输入 "y" 或 "Y" 来表示 "yes",输入 "n" 或 "N" 来表示 "no"。
    inquirer.prompt([
      {
        type: 'confirm',
        name: 'isAgree',
        message: 'Do you agree to the terms and conditions?',
        default: false
      }
    ]).then(answers => {
      console.log('Terms agreed:', answers.isAgree);
    });
    
  • list:

    • 提供一个下拉列表供用户选择。
    • 可以设置 pageSize 来控制一次显示的选项数量。
    inquirer.prompt([
      {
        type: 'list',
        name: 'chocolate',
        message: 'What is your favorite chocolate?',
        choices: ['Milk', 'Dark', 'White']
      }
    ]).then(answers => {
      console.log('Favorite chocolate:', answers.chocolate);
    });
    
  • rawlist:

    • 类型提供了一个有序的列表供用户选择,与 list 类型不同,rawlist 不会展示一个下拉菜单,而是将所有选项以纯文本形式展示在屏幕上。
    • 用户可以通过输入选项前的序号来选择答案。这种类型适用于选项数量不多,且用户需要快速浏览所有选项的场景。
    inquirer.prompt([
      {
        type: 'rawlist',
        name: 'iceCream',
        message: 'What is your favorite ice cream flavor?',
        choices: ['Vanilla', 'Chocolate', 'Strawberry']
      }
    ]).then(answers => {
      console.log('Favorite ice cream:', answers.iceCream);
    });
    
  • expand:

    • 允许用户选择一个选项,或者展开查看更多信息。
    • 可以设置 expand 属性为 true 来启用展开功能。
    inquirer.prompt([
      {
        type: 'expand',
        name: 'os',
        message: 'Which operating system do you use?',
        choices: [
          { key: 'w', name: 'Windows', value: 'windows' },
          { key: 'm', name: 'MacOS', value: 'macos' },
          { key: 'l', name: 'Linux', value: 'linux' }
        ]
      }
    ]).then(answers => {
      console.log('Operating system:', answers.os);
    });
    
  • checkbox:

    • 允许用户选择一个或多个选项。
    • 可以设置 choices 属性来定义可选的选项。
    inquirer.prompt([
      {
        type: 'checkbox',
        name: 'fruits',
        message: 'What fruits do you like?',
        choices: ['Apple', 'Banana', 'Cherry']
      }
    ]).then(answers => {
      console.log('Fruits liked:', answers.fruits);
    });
    
  • password

    • 用于输入密码,输入内容不会显示在屏幕上。
    • 可以设置 mask 属性来定义显示的占位符。
    inquirer.prompt([
      {
        type: 'password',
        name: 'password',
        message: 'Please enter your password',
        mask: '*'
      }
    ]).then(answers => {
      console.log('Password entered:', answers.password);
    });
    
  • editor

    • 允许用户在外部文本编辑器中输入多行文本。
    • 可以设置 editor 属性为 true 来启用编辑器。
    inquirer.prompt([
      {
        type: 'editor',
        name: 'text',
        message: 'Please write some text'
      }
    ]).then(answers => {
      console.log('Written text:', answers.text);
    });
    
  • range:

    • 允许用户选择一个数值范围。
    • 可以设置 min 和 max 属性来定义范围的最小值和最大值。
    inquirer.prompt([
      {
        type: 'range',
        name: 'age',
        message: 'How old are you?',
        min: 18,
        max: 99
      }
    ]).then(answers => {
      console.log('Age:', answers.age);
    });
    
  • autocomplete(需要安装 inquirer-autocomplete-prompt)

    • 允许用户输入文本,并提供自动完成的建议。
    • 可以设置 source 函数来定义自动完成的选项。
    import Autocomplete from 'inquirer-autocomplete-prompt';
    
    inquirer.registerPrompt('autocomplete', Autocomplete);
    
    inquirer.prompt([
      {
        type: 'autocomplete',
        name: 'city',
        message: 'What city do you live in?',
        source: (answersSoFar, input) => {
          // 这里可以是异步操作,比如从API获取数据
          return Promise.resolve(['New York', 'Los Angeles', 'Chicago'].filter(city => city.includes(input)));
        }
      }
    ]).then(answers => {
      console.log('City:', answers.city);
    });
    

额外插件

chalk: 一个用于美化终端字符串的库,可以添加颜色和样式。

cli-table: 一个用于在命令行中创建表格的库,有助于展示结构化数据。

figures: 一个提供常见特殊字符的库,比如箭号、复选框等,用于美化命令行输出。

log-symbols: 一个提供成功、错误、信息等不同日志级别符号的库。

ora: 一个用于在终端显示加载动画的库,常用于异步操作。

listr: 一个用于创建和管理任务队列的库,可以与 Inquirer.js 结合使用。

enquirer: Inquirer.js 的一个分支,提供了更多的问题类型和功能。

inquirer-autocomplete-prompt: 一个为 Inquirer.js 提供自动完成功能的插件。

inquirer-fuzzy-path: 一个允许用户通过模糊搜索选择文件或目录的插件。

inquirer-chalk-pipe: 一个让 Inquirer.js 提示输入支持 chalk-pipe 风格的字符串的插件。

inquirer-search-checkbox: 一个提供可搜索复选框的 Inquirer.js 插件。

inquirer-search-list: 一个提供搜索功能的列表选择器插件。

detect-port

用于检测某个端口是否可用。

安装

npm install detect-port

使用

import detect from "detect-port";
const port = 3000; // 你想要检测的端口号
detect(port).then((foundPort) => {
  // 这里一定要判断返回的端口是否是验证的端口,因为如果检测的端口被占用了,则会返回一个推荐的端口号回来
  if (port === foundPort) {
    console.log('端口' + port + '没有被占用');
  } else {
    console.log('端口' + port + '已被占用,系统为你分配了端口' + foundPort);
  }
}).catch(err => {
  console.error(err);
});

案列

目录结构

配置文件

export const config = [
  {
    name: "应用test1",
    port: 5173,
    application: "test1",
  },
  {
    name: "应用test2",
    port: 5174,
    application: "test2",
  },
  {
    name: "应用test3",
    port: 5175,
    application: "test3",
  },
  {
    name: "应用test4",
    port: 5176,
    application: "test4",
  },
  {
    name: "应用test5",
    port: 5177,
    application: "test5",
  },
  {
    name: "应用test6",
    port: 5178,
    application: "test6",
  },
];

交互式命令行脚本

import { execa } from "execa";
import detect from "detect-port";
import inquirer from "inquirer";
import { config } from "./config.js";
// 子应用
const subApps = [];
/**
 * 检查端口是否被占用
 * @param {*} list
 */
const checkPortOccupiedList = config.map((item) => {
  return new Promise((resolve, reject) => {
    detect(item.port).then((port) => {
      if (port === item.port) {
        resolve({
          ...item,
          disabled: false,
        });
      } else {
        resolve({
          ...item,
          disabled: true,
        });
      }
    });
  });
});
// 提问
const question = (apps) => {
  inquirer
    .prompt([
      {
        type: "checkbox",
        name: "apps",
        message: "请选择需要启动的子应用",
        choices: apps.map((item) => {
          return {
            name: `${item.name}:${item.port}`,
            value: item,
            disabled: item.disabled ? "已启动" : false,
          };
        }),
      },
    ])
    .then((answer) => {
      if (!answer.apps.length) {
        console.log("未选择应用");
        confirm(apps);
      }
      answer.apps.forEach((item) => {
        const subApp = execa(
          "pnpm",
          [`dev:${item.application}`, `--port`, `${item.port}`],
          {
            stdio: "inherit",
          }
        );
        subApp.on("error", (error) => {
          console.log(`${item.name} error:`, error);
        });
        subApp.on("exit", (exit) => {
          console.log(`${item.name} exit`);
        });
        subApps.push(subApp);
      });
    })
    .catch((error) => {
      console.log("error:", error);
    });
};
// 确定
const confirm = (apps) => {
  inquirer
    .prompt([
      {
        type: "confirm",
        message: "是否重新选择需要启动的子应用?",
        name: "confirm",
      },
    ])
    .then((answer) => {
      if (answer.confirm) {
        question(apps);
      } else {
        console.log("退出应用对话框");
        process.exit(0);
      }
    })
    .catch((error) => {
      console.log("error:", error);
    });
};

// 主应用
const base = (name, command, port) => {
  const baseServe = execa("pnpm", [command, "--port", port], {
    detached: true,
    stdout: "pipe",
    stream: true,
  });
  baseServe.on("error", (error) => {
    console.error(`error: ${error}`);
  });
  baseServe.on("close", (code) => {
    console.log(`${name}进程退出,状态码:${code}`);
    subApps.forEach((app) => {
      app.kill();
    });
    process.exit(0);
  });
};

// 启动服务
Promise.all(checkPortOccupiedList).then((ports) => {
  const baseServe = ports.shift();

  const name = baseServe.name;
  const command = `dev:` + baseServe.application;
  const port = "" + baseServe.port;

  if (!baseServe.disabled) {
    base(name, command, port);
  }
  question(ports);
});

package.json

{
  "name": "script",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "type": "module",
  "scripts": {
    "dev": "node index.js",
    "dev2": "node index2.js",
    "dev:test1": "vite dev applications/Test1",
    "dev:test2": "vite dev applications/Test2",
    "dev:test3": "vite dev applications/Test3",
    "dev:test4": "vite dev applications/Test4",
    "dev:test5": "vite dev applications/Test5",
    "dev:test6": "vite dev applications/Test6"
  },
  "keywords": [],
  "author": "",
  "license": "ISC",
  "dependencies": {
    "detect-port": "^1.6.1",
    "execa": "7.2.0",
    "inquirer": "^9.2.23",
    "vite": "^5.3.1"
  }
}

vite.config.ts

import { defineConfig } from "vite";
export default defineConfig(() => {
  return {
    root: ".",
  };
});

标签:脚本,console,name,answers,终端,inquirer,交互,port,log
From: https://www.cnblogs.com/letgofishing/p/18264383

相关文章

  • 【openGauss、PostgreSQL】openGauss、PostgreSQL数据库通用查表字段信息脚本-v202406
    【openGauss、PostgreSQL】openGauss、PostgreSQL数据库通用查表字段信息脚本-v20240620-2216openGauss、PostgreSQL数据库通用查表字段信息脚本-v20240620-2216openGauss、PostgreSQL数据库通用查表字段信息脚本-v20240620-2216此脚本,openGauss、PostgreSQL都可执......
  • 20-OWASP top10--XXS跨站脚本攻击
    目录什么是xxs?XSS漏洞出现的原因XSS分类反射型XSS储存型XSSDOM型XSSXSS漏洞复现XSS的危害或能做什么?劫持用户cookie钓鱼登录XSS获取键盘记录 同源策略(1)什么是跨域(2)同源策略(3)同源策略修改(允许所有人跨域访问)XSS绕过简单的绕过方法 使用HTML进行编码绕......
  • 一个使用Python和假设的天气API来获取和展示天气数据的简单脚本示例
    要使用Python编写一个天气预测的脚本,我们通常需要依赖于现有的天气API来获取实时或历史天气数据,并且结合机器学习或统计模型来进行预测。然而,由于天气预测是一个复杂的任务,通常需要大量的计算资源和专业的气象知识,这里我们将简化这个过程,只展示如何使用Python和一个假设的天......
  • 提升应用与外部设备的交互效率-案例
    目录概要案例背景较差的设计思路改进的设计思路小结概要计算机应用程序一旦与外部设备打交道,发送指令或者接收返回数据时,或多或少都会让程序员感到焦虑,因为设备特性不同,有的通过串口等标准端口传输,有的通过非标准的方式。非标准端口传输的数据获取,完全取决于设备......
  • 【JavaScript脚本宇宙】编写可靠代码:探索最佳JavaScript类型检查解决方案
    掌握类型安全:选择适合您的JavaScript类型检查工具前言JavaScript作为一种动态类型语言,在大型项目的开发中常常会遇到类型错误和难以调试的问题。为了解决这些问题,出现了各种类型的JavaScript类型检查工具。这些工具能够帮助开发人员在代码编写过程中及时发现潜在的类型错......
  • 申请SSL证书保姆级教程,包括FreeSSL申请、Acme脚本申请等方式。
    Acme脚本申请证书Acme脚本申请证书,是我们用到的最常见的一种证书的申请方式,它有很多的申请方法,大家只需要找到一种适合自己的也就好了。不管用下面的何种方式申请,都需要安装Acme,有一部分的申请场景需要用到相关的插件,所以我们需要提前安装。下面环境的安装方式,大家根据自己......
  • MySQL-5.7.38 基于二进制包一键安装脚本
    #!/bin/bash##********************************************************************#Author: Kevin#Date: 2024-06-23#FileName: install_mysql.sh#Description: Thetestscript#Copyright(C): 2024Allrightsreserved#****************************......
  • 【VMware vSphere】使用RVTools中的PowerShell脚本创建导出vSphere环境信息的自动化任
    RVTools是VMware生态系统中一个非常受欢迎且免费的Windows实用工具,用于收集并显示VMwarevSphere环境中的相关信息,如虚拟机、主机及集群等相关配置。RVTools利用VMwarevSphereManagementSDK8.0和CISRESTAPI提供的丰富数据来直接获取和收集信息,这在管理员对VMwa......
  • shell编程之条件语句(shell脚本)
    条件测试操作要使shell脚本程序具备一定的“智能”,面临的第一个问题就是如何区分不同的情况以确定执行何种操作。例如,当磁盘使用率超过95%时,发送告警信息;当备份目录不存在时,能够自动创建;当源码编译程序时,若配置失败则不再继续安装等。shell环境根据命令执行后的返回状态值($?)......
  • Python中的交互式GUI开发:与MATLAB uicontrol的比较
    Python中的交互式GUI开发Python中的交互式GUI开发:与MATLABuicontrol的比较**PythonGUI开发库****Tkinter****PyQt/PySide****与MATLAB的比较****总结**Python中的交互式GUI开发:与MATLABuicontrol的比较在MATLAB中,uicontrol是一个强大的功能,用于创建用户界面控......