GNU Readline库函数的应用示例

说明

GNU Readline是一个跨平台开源程序库,提供交互式的文本编辑功能。应用程序借助该库函数,允许用户编辑键入的命令行,并提供自动补全和命令历史等功能。Bash(Bourne Again Shell)、GDB、ftp 和mail等程序就使用Readline库提供其命令行界面。

Readline是GNU通用公共许可证版本3(GNU GPLv3)下的自由软件。这意味着若发布或分发的程序使用Readline库,则该程序必须是自由软件,并持有GPL-兼容的许可证。当然,用户也可使用自己的实现替代库的行为。

本文将基于若干典型的Readline库函数,给出一个简单的交互式调测器示例。示例代码的运行环境如下:

一  函数介绍

本节简要介绍后文将要用到的几个Readline库函数,如用来替代fgets()的readline()函数。关于库函数的更多描述可参考http://cnswww.cns.cwru.edu/php/chet/readline/rltop.html

readline()函数的ANSI C声明如下:


char *readline(const char *prompt);

该函数打印参数prompt 所指的提示字符串,然后读取并返回用户输入的单行文本(剔除换行符)。若prompt 为空指针或指向空字符串,则不显示任何提示。若尚未读取到字符就遇到EOF,则该函数返回NULL;否则返回由malloc()分配的命令行内存,故调用结束后应通过free()显式地释放内存。

读取命令行后,可调用add_history()函数将该行存入命令行历史列表中,以便后续重取。


void add_history(const char *string);

rl_completion_matches()函数用于自动补全:


typedef char *rl_compentry_func_t(const char *text, int state);

char ** rl_completion_matches(const char *text, rl_compentry_func_t *entry_func);

该函数返回一个字符串数组,即参数text的补全列表;若没有补全项,则函数返回NULL。该数组以空指针结尾,首个条目将替换text,其余条目为可能的补全项。函数指针entry_func指向一个具有两个参数的函数。其中参数state在首次调用时为零,后续调用时为非零。未匹配到text时,entry_func返回空指针。

函数指针rl_completion_entry_function指向rl_completion_matches()所使用的补全生成函数:


rl_compentry_func_t * rl_completion_entry_function;

若其值为空,则使用默认的文件名补全生成函数,即rl_filename_completion_function()。

应用程序可通过函数指针rl_attempted_completion_function自定义补全函数:


typedef char **rl_completion_func_t (const char *text, int start, int end);

rl_completion_func_t * rl_attempted_completion_function;

该变量指向创建匹配补全的替代函数。函数参数 start和 end为字符串缓冲区rl_line_buffer的下标,以定义参数text的边界。若该函数存在且返回NULL,或者该变量值被设置为NULL,则rl_complete()将调用rl_completion_entry_function指向的函数来生成匹配结果;除此之外,程序使用该变量所指函数返回的字符串数组。若该变量所指函数将变量rl_attempted_completion_function设置为非零值,则Readline将不执行默认的文件名补全(即使自定义补全函数匹配失败)。

二  示例代码

首先,自定义用户命令结构及其执行函数(为简化实现,执行函数仅打印函数名):

 1 //命令结构体
 2 typedef int (*CmdProcFunc)(void);
 3 typedef struct{
 4     char         *pszCmd;
 5     CmdProcFunc  fpCmd;
 6 }CMD_PROC;
 7
 8 //命令处理函数定义
 9 #define MOCK_FUNC(funcName) 10     int funcName(void){printf("  Enter "#funcName"!\n"); return 0;}
11
12 MOCK_FUNC(ShowMeInfo);
13 MOCK_FUNC(SetLogCtrl);
14 MOCK_FUNC(TestBatch);
15 MOCK_FUNC(TestEndianOper);

基于上述定义,创建命令列表如下:

 1 //命令表项宏,用于简化书写
 2 #define CMD_ENTRY(cmdStr, func)     {cmdStr, func}
 3 #define CMD_ENTRY_END               {NULL,   NULL}
 4
 5 //命令表
 6 static CMD_PROC gCmdMap[] = {
 7     CMD_ENTRY("ShowMeInfo",       ShowMeInfo),
 8     CMD_ENTRY("SetLogCtrl",       SetLogCtrl),
 9     CMD_ENTRY("TestBatch",        TestBatch),
10     CMD_ENTRY("TestEndian",       TestEndianOper),
11
12     CMD_ENTRY_END
13 };
14 #define CMD_MAP_NUM     (sizeof(gCmdMap)/sizeof(CMD_PROC)) - 1/*End*/

然后,提供两个检索命令列表的函数:

 1 //返回gCmdMap中的CmdStr列(必须为只读字符串),以供CmdGenerator使用
 2 static char *GetCmdByIndex(unsigned int dwCmdIndex)
 3 {
 4     if(dwCmdIndex >=  CMD_MAP_NUM)
 5         return NULL;
 6     return gCmdMap[dwCmdIndex].pszCmd;
 7 }
 8
 9 //执行命令
10 static int ExecCmd(char *pszCmdLine)
11 {
12     if(NULL == pszCmdLine)
13         return -1;
14
15     unsigned int dwCmdIndex = 0;
16     for(; dwCmdIndex < CMD_MAP_NUM; dwCmdIndex++)
17     {
18         if(!strcmp(pszCmdLine, gCmdMap[dwCmdIndex].pszCmd))
19             break;
20     }
21     if(CMD_MAP_NUM == dwCmdIndex)
22         return -1;
23     gCmdMap[dwCmdIndex].fpCmd(); //调用相应的函数
24
25     return 0;
26 }

以上代码独立于Readline库,接下来将编写与该库相关的代码。

考虑到实际应用中,程序运行平台不一定包含Readline库,因此需要用__READLINE_DEBUG条件编译控制库的使用。

  1 #ifdef __READLINE_DEBUG
  2 #include <readline/readline.h>
  3 #include <readline/history.h>
  4
  5 static const char * const pszCmdPrompt = "clover>>";
  6
  7 //退出交互式调测器的命令(不区分大小写)
  8 static const char *pszQuitCmd[] = {"Quit", "Exit", "End", "Bye", "Q", "E", "B"};
  9 static const unsigned char ucQuitCmdNum = sizeof(pszQuitCmd) / sizeof(pszQuitCmd[0]);
 10 static int IsUserQuitCmd(char *pszCmd)
 11 {
 12     unsigned char ucQuitCmdIdx = 0;
 13     for(; ucQuitCmdIdx < ucQuitCmdNum; ucQuitCmdIdx++)
 14     {
 15         if(!strcasecmp(pszCmd, pszQuitCmd[ucQuitCmdIdx]))
 16             return 1;
 17     }
 18
 19     return 0;
 20 }
 21
 22 //剔除字符串首尾的空白字符(含空格)
 23 static char *StripWhite(char *pszOrig)
 24 {
 25     if(NULL == pszOrig)
 26         return "NUL";
 27
 28     char *pszStripHead = pszOrig;
 29     while(isspace(*pszStripHead))
 30         pszStripHead++;
 31
 32     if(‘\0‘ == *pszStripHead)
 33         return pszStripHead;
 34
 35     char *pszStripTail = pszStripHead + strlen(pszStripHead) - 1;
 36     while(pszStripTail > pszStripHead && isspace(*pszStripTail))
 37         pszStripTail--;
 38     *(++pszStripTail) = ‘\0‘;
 39
 40     return pszStripHead;
 41 }
 42
 43 static char *pszLineRead = NULL;  //终端输入字符串
 44 static char *pszStripLine = NULL; //剔除前端空格的输入字符串
 45 char *ReadCmdLine()
 46 {
 47      //若已分配命令行缓冲区,则将其释放
 48     if(pszLineRead)
 49     {
 50         free(pszLineRead);
 51         pszLineRead = NULL;
 52     }
 53     //读取用户输入的命令行
 54     pszLineRead = readline(pszCmdPrompt);
 55
 56     //剔除命令行首尾的空白字符。若剔除后的命令不为空,则存入历史列表
 57     pszStripLine = StripWhite(pszLineRead);
 58     if(pszStripLine && *pszStripLine)
 59         add_history(pszStripLine);
 60
 61     return pszStripLine;
 62 }
 63
 64 static char* CmdGenerator(const char *pszText, int dwState)
 65 {
 66     static int dwListIdx = 0, dwTextLen = 0;
 67     if(!dwState)
 68     {
 69         dwListIdx = 0;
 70         dwTextLen = strlen(pszText);
 71     }
 72
 73     //当输入字符串与命令列表中某命令部分匹配时,返回该命令字符串
 74     const char *pszName = NULL;
 75     while((pszName = GetCmdByIndex(dwListIdx)))
 76     {
 77         dwListIdx++;
 78
 79         if(!strncmp (pszName, pszText, dwTextLen))
 80             return strdup(pszName);
 81     }
 82
 83     return NULL;
 84 }
 85
 86 static char** CmdCompletion (const char *pszText, int dwStart, int dwEnd)
 87 {
 88     //rl_attempted_completion_over = 1;
 89     char **pMatches = NULL;
 90     if(0 == dwStart)
 91         pMatches = rl_completion_matches(pszText, CmdGenerator);
 92
 93     return pMatches;
 94 }
 95
 96 //初始化Tab键能补齐的Command函数
 97 static void InitReadLine(void)
 98 {
 99     rl_attempted_completion_function = CmdCompletion;
100 }
101
102 #endif

自动补全后的命令字符串结尾多出一个空格,故需调用StripWhite将该空格剔除。

最后,可编写交互式调测函数如下:

 1 int main(void)
 2 {
 3 #ifndef __READLINE_DEBUG
 4     printf("Note: Macro __READLINE_DEBUG is Undefined, thus InteractiveCmd is Unavailable!!!\n\n");
 5 #else
 6     printf("Note: Welcome to Interactive Command!\n");
 7     printf("      Press ‘Quit‘/‘Exit‘/‘End‘/‘Bye‘/‘Q‘/‘E‘/‘B‘ to quit!\n\n");
 8     InitReadLine();
 9     while(1)
10     {//也可加入超时机制以免忘记退出
11         char *pszCmdLine = ReadCmdLine();
12         if(IsUserQuitCmd(pszCmdLine))
13         {
14             free(pszLineRead);
15             break;
16         }
17
18         ExecCmd(pszCmdLine);
19     }
20 #endif
21
22     return 0;
23 }

该函数用法类似Shell,便于定制调测命令的随机执行。命令中首个参数(本文参数唯一)支持自动补全,但参数区分大小写。

编译链接时需加载readline库和termcap(或ncurses)库。ncurses库通常使用terminfo(终端信息),少数实现会使用termcap(终端能力)。启用Readline库时,执行结果如下:

 1 [[email protected] test1]$ gcc -Wall -o ReadLine ReadLine.c -D__READLINE_DEBUG -lreadline -lncurses
 2 [[email protected] test1]$ ./ReadLine
 3 Note: Welcome to Interactive Command!
 4       Press ‘Quit‘/‘Exit‘/‘End‘/‘Bye‘/‘Q‘/‘E‘/‘B‘ to quit!
 5
 6 clover>>ShowMeInfo(完整输入)
 7   Enter ShowMeInfo!
 8 clover>>ShowMeInfo(UP键调出历史命令)
 9   Enter ShowMeInfo!
10 clover>>SetLogCtrl (输入‘Se‘自动补全)
11   Enter SetLogCtrl!
12 clover>>   TestEndianOper(错误输入)
13 clover>>TestEndian  (输入‘T‘自动补全为"Test",再输入‘E‘自动补全为"TestEndian ")
14   Enter TestEndianOper!
15 clover>>  TestBatch   (命令首尾加空格,无法自动补全)
16   Enter TestBatch!
17 clover>>ReadLine (输入‘R‘自动补全文件名)
18 clover>>quit
19 [[email protected] test1]$

不启用Readline库时,执行结果如下:

1 [[email protected] test1]$ gcc -Wall -o ReadLine ReadLine.c
2 ReadLine.c:41: warning: ‘GetCmdByIndex‘ defined but not used
3 ReadLine.c:49: warning: ‘ExecCmd‘ defined but not used
4 [[email protected] test1]$ ./ReadLine
5 Note: Macro __READLINE_DEBUG is Undefined, thus InteractiveCmd is Unavailable!!!

GNU Readline库函数的应用示例

时间: 2024-10-29 08:04:29

GNU Readline库函数的应用示例的相关文章

GNU Readline 库及编程简介

用过 Bash 命令行的一定知道,Bash 有几个特性: TAB 键可以用来命令补全 ↑ 或 ↓ 键可以用来快速输入历史命令 还有一些交互式行编辑快捷键: C-A / C-E 将光标移到行首/行尾 C-B / C-F 将光标向左/向右移动一个位置 C-D 删除光标下的一个字符 C-K 删除光标及光标到行尾的所有字符 C-U 删除光标到行首的所有字符 ... 同样的操作在很多交互式程序都有类似的操作,例如 ftp.gdb 等等,那么你是否想过这些是如何实现的呢?如果我们要做一个命令行下的交互式开源

rlwrap安装报错You need the GNU readline 解决方法

首先大家肯定知道rlwrap是干什么的? 在linux以及unix中,sqlplus的上下左右.回退无法使用,会出现乱码情况.而rlwrap这个软件就是用来解决这个的. 这个错误曾经困扰我很久很久!下载readline 安装,安装好readline 还是无法运行rlwrap的安装程序 在网上搜寻很久才找到一个靠谱的解决方法 原来rlwrap这个程序的安装,需要依赖两个包, 一个是readline,这个readline在RHEL中已经集成了.不需要去专门下载 另外一个包 libtermcap-de

Python2.7字符编码详解

一. 字符编码基础 为明确概念,将字符集的编码模型分为以下4个层次: 抽象字符清单(Abstract Character Repertoire, ACR):待编码文字和符号的无序集合,包括各国文字.标点.图形符号.数字等. 已编码字符集(Coded Character Set, CCS):从抽象字符清单到非负整数码点(code point)集合的映射. 字符编码格式(Character Encoding Form, CEF):从码点集合到指定宽度(如32比特整数)编码单元(code unit)的

守护进程接收终端输入的一种变通性方法

说明 本文主要介绍某嵌入式产品中DSL用户态驱动模块作为守护进程时,如何接收终端输入的变通性方法. 出于信息安全考虑,文中涉及系统方面的接口函数未给出实现细节,但不影响表述的完整性. 相关性文章参见<GNU Readline库函数的应用示例>. 一  背景知识 init进程(如Busybox init)是嵌入式系统内核自举时启动的第一个也是惟一的用户进程.init进程是后续所有其他进程的父进程(其进程ID为1),在系统运行期间以守护进程的形式一直存在.它主要负责启动各运行层次特定的系统服务(如

readline安装

wget -c ftp://ftp.gnu.org/gnu/readline/readline-6.2.tar.gz tar -zxvf readline-6.2.tar.gz cd readline-6.2 ./configure           //这个地方一定要指定路径,要不然要报错(一般在/usr/local下用软件命名,比如 /usr/local/readline下) make && make installl ldconfig 或: yum -y install readl

My.Ioc 代码示例——谈一谈如何实现装饰器 (Decorator) 模式,兼谈如何扩展 My.Ioc

装饰器模式体现了一种“组合优于继承”的思想.当我们要动态为对象增加新功能时,装饰器模式往往是我们的好帮手. 很多后期出现的 Ioc 容器都为装饰器模式提供了支持,比如说 Autofac.在 My.Ioc 中,默认不提供装饰器支持,但我们可以自己进行扩展,以提供此项功能. using System; using System.Collections.Generic; using System.Diagnostics; using System.Reflection; using My.Ioc; u

【原创】lua编译时发现缺少readline库

编译lualua项目,其中用到了lua-5.1版本的源码,编译时提示缺少readline库,找不到readline/readline.h头文件等 发现系统中其实有安装readline库不过没有做链接和头文件目录拷贝,anyway不管这些,就当没有,重新安装readline库 1.下载readline-6.2.tar.gz wget -c ftp://ftp.gnu.org/gnu/readline/readline-6.2.tar.gz 放到某个目录下,如/usr/local/ 并解压出来 ta

为 STM32 移植 Berry 脚本语言

Berry 是我为单片机设计的一款脚本语言,该语言具有资源占用小.平台无关.执行速度快和易于掌握等优点.在单片机上使用脚本语言可以提高单片机的二次开发能力以及调试效率,同时也是一种比较新颖的玩法.本教程将简要介绍在 STM32F103RBT6 单片机上移植 Berry 脚本语言的方法.教程的末尾给出了移植完成的示例工程,读者可以根据本教程的内容和示例工程完成自己的移植工作. 我使用 ST 推出的 CubeMX 软件进行单片机固件库的配置,选择用 CubeMX 生成 HAL 库工程而不用标准库是考

PHP 5.6.15 编译安装

1.准备安装文件 php-5.6.15.tar http://php.net/downloads.php 2.准备安装环境和必须的包     yum install -y libxml2-devel openssl-devellibcurl-devel libjpeg-devel libpng-devel libicu-devel openldap-devel     yum install gcc gcc-c++ #编译工具 如果想让编译的php支持mcrypt扩展,需安装libmcrypt