linux多级菜单脚本教程,Linux下使用readline库编程实现多级CLI菜单

2023-05-16

一、背景

CLI是一种快速简洁的人机交互方式,优秀的CLI(如 mysql、vtysh、gdb)带给我们非常好的体验。那么CLI都是如何开发出来的?

二、相关知识

2.1 CLI vs GUI

文章[1] 纵观CLI与GUI的发展进行比对:CLI命令行交互对于使用者而言,就是专业、高效;而GUI界面式的交互就是直观、易用;

2.2 readline

CLI的开发中可以借助 readline库提高输入的体验性,如文章[3]所分析,在Bash的使用中经常用到的:

tab自动补齐;

上下查看历史命令;

光标移动、输入删除;

这些特性均可以由 readline库进行提供,关于API的使用可以参考官方文档-文章[2];

三、实现

本实现根据 readline/example/fileman.c 案例进行修改;

考虑设计多级菜单选项时,需要通过提示符进行切换,如 "system >"、"system (route) >"、"system (route-config) >"提示所在的菜单项;

并且在每个菜单项下,需要支持不同的命令集,对不同的命令进行相应操作,如 open 加载配置、write 保存配置、quit返回上级、exit 退出程序等操作;

所以就在上下文数据结构上,使用下图的这种结构:

0818b9ca8b590ca3270a3433284dd417.png

typedef struct command

{

char name[SIZE_NAME_NORMAL];

int (*callback)(void *, char *);

void *args;

char info[SIZE_NAME_LONG];

} command_t;

typedef struct menu

{

char prompt[SIZE_NAME_NORMAL];

size_t cmd_size;

struct command *pcmd;

} menu_t;

typedef struct instance

{

u8 enable;

enum menu_e {

MENU_1 = 0,

MENU_2,

MENU_3,

MENU_MAX,

} menu_idx;

struct menu menu[MENU_MAX];

} instance_t;为了便于结构的查看,对各个菜单使用 menu1、2、3进行抽象;然后对应各个菜单定义提示内容、命令集;

static instance_t g_inst = {

.enable = 1,

.menu = {

[ MENU_1 ] = {"menu_1 > ", 0, NULL},

[ MENU_2 ] = {"menu_2 > ", 0, NULL},

[ MENU_3 ] = {"menu_3 > ", 0, NULL},

}

};

static command_t g_menu1_cmd[] = {

{"cmd_1_2", cmd_1_2, &g_inst, "Jump to menu2"},

{"cmd_1_3", cmd_1_3, &g_inst, "Jump to menu3"},

{"exit", cmd_exit, &g_inst, "Exit program"},

{"help", cmd_help, &g_inst, "Help message"},

{"?", cmd_help, &g_inst, "Help message"},

};

static command_t g_menu2_cmd[] = {

{"cmd_2_1", cmd_2_1, &g_inst, "Jump to menu1"},

{"cmd_2_3", cmd_2_3, &g_inst, "Jump to menu3"},

{"exit", cmd_exit, &g_inst, "Exit program"},

{"help", cmd_help, &g_inst, "Help message"},

{"?", cmd_help, &g_inst, "Help message"},

};然后就考虑菜单之间的切换了,这里是利用 menu_idx 对菜单状态进行一个维护,即切换菜单时修改 menu_idx,对应的上下文随着改变

所以在 cmd1_2\2_3\3_1 里面是对 menu_idx 进行修改的;

另外的侧重点就是,如何使用readline执行命令、如何使用readline自动补齐;

初始化、循环获取输入行的入口函数:

static void __do_init()

{

g_inst.menu_idx = MENU_1;

g_inst.menu[MENU_1].pcmd = g_menu1_cmd;

g_inst.menu[MENU_2].pcmd = g_menu2_cmd;

g_inst.menu[MENU_3].pcmd = g_menu3_cmd;

g_inst.menu[MENU_1].cmd_size = sizeof(g_menu1_cmd) / sizeof(command_t);

g_inst.menu[MENU_2].cmd_size = sizeof(g_menu2_cmd) / sizeof(command_t);

g_inst.menu[MENU_3].cmd_size = sizeof(g_menu3_cmd) / sizeof(command_t);

}

void readline_init()

{

/* Allow conditional parsing of the ~/.inputrc file. */

rl_readline_name = "readline_history";

/* Tell the completer that we want a crack first. */

rl_attempted_completion_function = readline_completion;

__do_init();

}

void readline_loop()

{

char *pline = NULL;

char *ps = NULL;

for ( g_inst.enable = 1; g_inst.enable; ) {

pline = readline(g_inst.menu[g_inst.menu_idx].prompt);

if ( !pline ) {

break;

}

ps = __do_stripwhite(pline);

if ( ps ) {

add_history(ps);

__do_cmd_execute(&g_inst.menu[g_inst.menu_idx], ps);

}

free(pline);

}

}

int main(int argc, char *argv[])

{

readline_init();/* Bind our completer. */

readline_loop();

return EXIT_SUCCESS;

}

获取输入行成功后,立刻进行命令匹配、执行命令回调函数:

static int __do_cmd_execute(menu_t *pmenu, char *line)

{

int ix = 0;

command_t *pcmd = NULL;

char *word = NULL;

/* Isolate the command word. */

while ( line[ix] && whitespace(line[ix]) ) {

ix++;

}

word = line + ix;

while ( line[ix] && !whitespace(line[ix]) ) {

ix++;

}

if ( line[ix] ) {

line[ix++] = '\0';

}

pcmd = command_match(pmenu->pcmd, pmenu->cmd_size, word);

if ( !pcmd ) {

fprintf (stderr, "%s: Unknow command.\n", word);

return FAILURE;

}

/* Get argument to command, if any. */

while ( whitespace(line[ix]) ) {

ix++;

}

word = line + ix;

return ((*(pcmd->callback))(pcmd->args, word));

}

static char *__do_stripwhite(char *string)

{

char *s, *t;

for (s = string; whitespace (*s); s++)

;

if ( *s == 0 ) {

return s;

}

t = s + strlen (s) - 1;

while ( t > s && whitespace (*t) ) {

t--;

}

*++t = '\0';

return s;

}

command_t *command_match(command_t *pcmd, size_t size, char *name)

{

int ix = 0;

if ( !name ) {

return NULL;

}

for ( ix = 0; pcmd[ix].name, ix < size; ix++ ) {

if ( !strcmp(name, pcmd[ix].name) ) {

LOGD("Match: %s\n", name);

return &pcmd[ix];

}

}

return NULL;

}

上述提到的命令执行的过程,下面则要说一下命令的自动补齐功能:

/* Generator function for command completion. STATE lets us know whether

to start from scratch; without any state (ix.e. STATE == 0), then we

start at the top of the list. */

static char *__do_cmd_generator(const char *text, int state)

{

static int cmd_idx, len;

char *name;

menu_t *pmenu = &g_inst.menu[g_inst.menu_idx];

/* If this is a new word to complete, initialize now. This includes

saving the length of TEXT for efficiency, and initializing the index

variable to 0. */

if ( !state ) {

cmd_idx = 0;

len = strlen(text);

}

/* Return the next name which partially matches from the command list. */

while ( name = pmenu->pcmd[cmd_idx].name ) {

if ( cmd_idx++ >= pmenu->cmd_size ) {

break;

}

if ( strncmp(name, text, len) == 0 ) {

/* Readline frees the strings when it has finished with them */

return (strdup(name));

}

}

/* If no names matched, then return NULL. */

return NULL;

}

四、总结

需要注意的是,readline 的用户接口函数 rl_completion_matches() 产生自动补齐的列表;

内部函数 rl_completion_matches() 使用程序提供的 generator 函数来产生补全列表,并返回这些匹配的数组进行显示;

在此之前需要将 generator 函数的地址放到 rl_completion_entry_function 变量中,例如上面的命令补全函数就是不同的 __do_cmd_generator(),注意返回值需申请新的指针空间;

同时,readline 库中有个变量 rl_attempted_completion_function,改变量类型是一个函数指针rl_completion_func_t *,我们可以将该变量设置我们自定义的产生匹配的函数,并绑定到 TAB 键的回调;

参考文章:

[1] http://www.cnitblog.com/addone/archive/2008/01/08/38581.html

[2] http://cnswww.cns.cwru.edu/php/chet/readline/readline.html

[3] http://www.cnblogs.com/hazir/p/instruction_to_readline.html

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

linux多级菜单脚本教程,Linux下使用readline库编程实现多级CLI菜单 的相关文章

  • navicat 查看表的注释

    2019独角兽企业重金招聘Python工程师标准 gt gt gt navicat 不同于sqlyog 没有直接展示注释的地方 xff0c 通过查看DDL 来查看注释 查看DDL的方式 打开Navicat工具 xff0c 双击打开需要连接的
  • R语言ggplot2绘图设置X轴刻度,字体大小及绘图区大小

    gt colnames data1 seq 2 ncol data1 15 1 34 AAAA 34 34 AAGG 34 34 ATGC 34 34 ACGT 34 34 AGGA 34 34 TACG 34 34 TTCC 34 34
  • 无法连接虚拟设备sata0:1,因为主机上没有相应的设备

    打开虚拟机的时候 xff0c 弹出这个 xff0c 但是虚拟机可以正常使用 无法连接虚拟设备sata0 1 xff0c 因为主机上没有相应的设备 原因是因为开机连接CD DVD驱动器 去掉这个勾 OK 转载于 https www cnblo
  • 计算机网络第七版谢希仁知识点总结

    1 专有名词 xff1a 互联网服务提供商ISP xff08 Interest Service Provider xff09 互联网交换点 IXP xff08 Internet eXchange Point xff09 广域网WAN xff
  • SecureCRT 通过Xmanager 开启图形界面(root用户登录后,su登录oracle用户)

    一 安装了Xmanager软件 xff0c 在客户端上开启了Xmanager Passive 程序 二 SecureCRT的会话选项中 端口转发 远程 X11 里将转发X11数据包勾上 用root用户登录后运行xclock程序 xff0c
  • 多线程大串讲之二: 多线程同步的学习[1]

    一 CriticalSection 临界区 临界区 34 CriticalSection 当把一段代码放入一个临界区 线程执行到临界区时就独占了 让其他也要执行此代码的线程先等等 这和前面用的 Lock 和 UnLock 差不多 使用格式如
  • matlab练习程序(加权最小二乘)

    起本篇题目还是比较纠结的 xff0c 原因是我本意打算寻找这样一个算法 xff1a 在测量数据有比较大离群点时如何估计原始模型 上一篇曲面拟合是假设测量数据基本符合均匀分布 xff0c 没有特别大的离群点的情况下 xff0c 我们使用最小二
  • linux搜索一个文件

    find name filename 转载于 https www cnblogs com tiandsp archive 2012 07 15 2592088 html
  • VINS 回环检测与全局优化

    回环检测 VINS回环检测与全局优化都在pose graph cpp内处理 首先在pose graph node加载vocabulary文件给BriefDatabase用 xff0c 如果要加载地图 xff0c 会loadPoseGraph
  • gcc make 与cmake

    1 gcc xff08 1 xff09 是什么 xff1f 它是GNU Compiler Collection xff08 就是GNU编译器套件 xff09 xff0c 也可以简单认为是编译器 它可以编译很多种编程语言 xff08 括C C
  • server unexpectedly closed network connection

    在使用 ssh 登入 Linux 時 xff0c 卻發生了 server unexpectedly closed network connection 的狀況 解决方法 xff1a 1 修改 etc ssh sshd config 將 Us
  • 开源SDN控制器和商用SDN控制器一览

    以下是来自IT168收集的2014年可追寻到的开源SDN控制器和商用SDN控制器 xff0c 此处转载以供大家知悉 xff0c 更多控制器信息需要大家自己动手去查询和理解 开源SDN控制器组织 随机排序 xff0c 仅为了查看便利 1 组织
  • openstack 异常处理

    1 xff0c keystone 验证失败 xff0c 例如 xff1a Authorization failed The request you have made requires authentication from 172 16
  • [原]C++头文件的包含顺序研究

    作者 xff1a 朱金灿 来源 xff1a http blog csdn net clever101 一 xff0e Google C 43 43 编程风格指南 里的观点 公司在推行编码规范 xff0c 领导提议基本上使用 Google C
  • 使用badblocks检测坏块

    命令格式 badblocks svw b lt 区块大小 gt o lt 输出文件 gt 磁盘装置 磁盘区块数 启始区块 典型的命令如下 写测试 数据安全 sudo badblocks n b 4096 c 16 s dev sda o h
  • Soft NMS改进的非极大值抑制方法的pytorch cuda版本,可以用

    话不多说 xff0c 直接上代码 xff0c 代码是根据soft max论文提供的代码 xff0c 修改的 xff0c 原来的代码是cpu版本 xff0c 且返回的keep参数并不是原来bbox的序列参数 xff0c 所以做了部分修改 xf
  • redis session时,sessionId作为token,可靠实现

    场景 xff1a 在一些不能使用session xff0c 或者session不能保持的情况 xff0c 通常服务器端产生一个token字符串标识用户登录状态 当前端调用后端接口时 xff0c 将此token作为参数加入到请求中 xff0c
  • 好朋友简简单单,好情谊清清爽爽,好缘份久久长长

    好朋友简简单单 xff0c 好情谊清清爽爽 xff0c 好缘份久久长长 A good friend is simple a good friendship is refreshing a good relationship lasts fo
  • LwIP raw api下使用tcp keep alive

    First we should enable this macro in lwipopts h define LWIP TCP KEEPALIVE 1 The following code is implemented after tcp
  • 阿里云API网关(9)常见问题

    网关指南 xff1a https help aliyun com document detail 29487 html spm 61 5176 doc48835 6 550 23Oqbl 网关控制台 xff1a https apigatew

随机推荐