一、背景

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 退出程序等操作;

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

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

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

  1. linux多级菜单脚本教程,linux shell 编写菜单脚本事例

    menu2文件代码: --- #!/bin/sh #menu2 #Main menu script #ignore ctrl-c and QUIT interrupts trap "&quo ...

  2. linux c read函数返回值,Linuxc - GNU Readline 库及编程简介

    GNU Readline 库及编程简介 简介 用过 Bash 命令行的一定知道,Bash 有几个特性: TAB 键可以用来命令补全 ↑ 或 ↓ 键可以用来快速输入历史命令 还有一些交互式行编辑快捷键: ...

  3. linux系统nginx安装教程,Linux下Nginx安装教程

    Linux下Nginx安装教程分享,具体内容如下 1.安装编译文件及库文件 yum -y install make zlib zlib-devel gcc-c++ libtool openssl op ...

  4. linux python2.7安装教程_Linux下python升级至2.7步骤

    首先下载源tar包 可利用linux自带下载工具wget下载,如下所示: 下载完成后到下载目录下,解压 1 tar -zxvf Python-2.7.3.tgz 2 进入解压缩后的文件夹 1 cd P ...

  5. linux apache 手动安装教程,linux下手动安装apache

    下面是官方文档的要求,必须安装APR.APR-Util.PCRE,gcc-c++等包,文档URL地址http://httpd.apache.org/docs/2.4/install.html. 所以, ...

  6. linux进程和线程教程,Linux下查看进程和线程

    在Linux中查看线程数的三种方法 1.top -H 手册中说:-H : Threads toggle 加上这个选项启动top,top一行显示一个线程.否则,它一行显示一个进程. 2.ps xH 手册 ...

  7. linux系统声卡安装教程,Linux系统下如何安装声卡驱动?

    装了几次Linux OS,当然也装了几次声卡驱动,一般来说都是安装ALSA(Adcance Linux Sound Architecture)驱动,多装几次以后就会发现非常的简单的. 首先,先决条件, ...

  8. linux系统 安卓系统安装教程,Linux系统下安装android sdk的方法步骤

    本文阐述的是如何在Linux系统中安装Android SDK 环境,下面话不多说,来看看详细的介绍吧. 直接下载解压: wget http://dl.google.com/android/androi ...

  9. linux中调试脚本,在Linux下调试 Shell 脚本

    在大多数编程语言中都有调试工具可用于调试. 调试工具可以运行需要调试的程序或脚本,使我们可以在运行时检查脚本或程序的内部执行过程. 在shell脚本中我们没有任何调试工具,只能借助命令行选项(-n,- ...

  10. linux后台启动脚本nohup,linux下后台执行shell脚本nohup

    [GSM]GTM900C的应用--短信 虽说GSM已经很老旧,但其低廉的价格,非常适合一些需要小数据上网传输和短信等功能的应用场合. 不知道GSM能否像51单片机一样,在低端应用中长久不衰.GTM90 ...

最新文章

  1. Git push file exceed GitHub's file size
  2. 模拟撞击_正确看待小行星威胁!NASA模拟8年后300米小行星撞击地球试验
  3. MATLAB实战系列(二十一)-基于遗传算法的BP神经网络优化算法(附MATLAB代码)
  4. 贪婪算法近似集合覆盖问题的解
  5. oracle数据库12下载地址,Oracle 数据库和补丁下载地址 12.1.0.2 11.2.0.4 11.2.0.1
  6. MyBatis知多少(8)关系型数据库
  7. 安装CentOS时提示an error has occurred - no valid devices were fo
  8. 创业型 APP 如何筛选合适的推送平台
  9. 【第七篇】Vue实战综合案例
  10. 以太网物理层信号测试与分析
  11. 音频线是什么 音频线如何连接
  12. python应用程序无法正常启动0xc000007b_应用程序无法正常启动0xc000007b解决方法
  13. [人工智能-深度学习-33]:卷积神经网络CNN - 常见分类网络- LeNet网络结构分析与详解
  14. 北邮计算机学院国家示范,北京邮电大学获批2020年国家自然科学基金81项
  15. jmeter+csv+ant接口自动化测试--设计jmeter脚本(一)
  16. 一文读懂程序化交易算法交易量化投资高频交易统计利
  17. 有趣的马氏链及其平稳分布
  18. 2023最新SSM计算机毕业设计选题大全(附源码+LW)之java疫情防控管理系统02vsf
  19. 用matlab绘制克莱因瓶,完美!年前学会SketchUp建模克莱因瓶
  20. 52 Movies Every Tech Geek Must See

热门文章

  1. Swing开发之JButton篇
  2. 本地词库翻译php,有道词典词库(您也可以轻松翻译离线的有道词典词库)
  3. 【微信小程序】(一)创建项目与前端编写
  4. 目标检测系列1——Overfeat
  5. 【新品发布】行业领先数据恢复“先锋”震撼来袭
  6. ltspice导入spice模型_LTspice 怎么自建 MOSFET 模型
  7. 给大家讲解一下 AIDL原理分析
  8. 思维模型 时间管理矩阵
  9. mysql 视频教程下载_最全138节Mysql数据库+PHP零基础到精通视频教程【云盘下载】...
  10. 在群晖(Synology) 中运行115网盘Linux版(docker)