GNU Readline库函数的应用示例
摘自:https://www.cnblogs.com/clover-toeic/p/3892688.html
说明
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_over设置为非零值,则Readline将不执行默认的文件名补全(即使自定义补全函数匹配失败)。
二 示例代码
1 #include <stdio.h> 2 #include <stdlib.h> 3 #include <string.h> 4 5 #define __READLINE_DEBUG 6 7 8 9 // 首先,自定义用户命令结构及其执行函数(为简化实现,执行函数仅打印函数名): 10 //命令结构体 11 typedef int (*CmdProcFunc)(void); 12 typedef struct{ 13 char *pszCmd; 14 CmdProcFunc fpCmd; 15 }CMD_PROC; 16 17 //命令处理函数定义 18 #define MOCK_FUNC(funcName) \ 19 int funcName(void){printf(" Enter "#funcName"!\n"); return 0;} 20 21 MOCK_FUNC(ShowMeInfo); 22 MOCK_FUNC(SetLogCtrl); 23 MOCK_FUNC(TestBatch); 24 MOCK_FUNC(TestEndianOper); 25 26 27 28 29 // 基于上述定义,创建命令列表如下: 30 31 //命令表项宏,用于简化书写 32 #define CMD_ENTRY(cmdStr, func) {cmdStr, func} 33 #define CMD_ENTRY_END {NULL, NULL} 34 35 //命令表 36 static CMD_PROC gCmdMap[] = { 37 CMD_ENTRY("ShowMeInfo", ShowMeInfo), 38 CMD_ENTRY("SetLogCtrl", SetLogCtrl), 39 CMD_ENTRY("TestBatch", TestBatch), 40 CMD_ENTRY("TestEndian", TestEndianOper), 41 42 CMD_ENTRY_END 43 }; 44 #define CMD_MAP_NUM (sizeof(gCmdMap)/sizeof(CMD_PROC)) - 1/*End*/ 45 46 47 48 49 50 51 // 然后,提供两个检索命令列表的函数: 52 53 //返回gCmdMap中的CmdStr列(必须为只读字符串),以供CmdGenerator使用 54 static char *GetCmdByIndex(unsigned int dwCmdIndex) 55 { 56 if(dwCmdIndex >= CMD_MAP_NUM) 57 return NULL; 58 return gCmdMap[dwCmdIndex].pszCmd; 59 } 60 61 //执行命令 62 static int ExecCmd(char *pszCmdLine) 63 { 64 if(NULL == pszCmdLine) 65 return -1; 66 67 unsigned int dwCmdIndex = 0; 68 for(; dwCmdIndex < CMD_MAP_NUM; dwCmdIndex++) 69 { 70 if(!strcmp(pszCmdLine, gCmdMap[dwCmdIndex].pszCmd)) 71 break; 72 } 73 if(CMD_MAP_NUM == dwCmdIndex) 74 return -1; 75 gCmdMap[dwCmdIndex].fpCmd(); //调用相应的函数 76 77 return 0; 78 } 79 80 81 82 83 // 以上代码独立于Readline库,接下来将编写与该库相关的代码。 84 // 考虑到实际应用中,程序运行平台不一定包含Readline库,因此需要用__READLINE_DEBUG条件编译控制库的使用。 85 86 87 88 #ifdef __READLINE_DEBUG 89 #include <readline/readline.h> 90 #include <readline/history.h> 91 92 static const char * const pszCmdPrompt = "clover>>"; 93 94 //退出交互式调测器的命令(不区分大小写) 95 static const char *pszQuitCmd[] = {"Quit", "Exit", "End", "Bye", "Q", "E", "B"}; 96 static const unsigned char ucQuitCmdNum = sizeof(pszQuitCmd) / sizeof(pszQuitCmd[0]); 97 static int IsUserQuitCmd(char *pszCmd) 98 { 99 unsigned char ucQuitCmdIdx = 0; 100 for(; ucQuitCmdIdx < ucQuitCmdNum; ucQuitCmdIdx++) 101 { 102 if(!strcasecmp(pszCmd, pszQuitCmd[ucQuitCmdIdx])) 103 return 1; 104 } 105 106 return 0; 107 } 108 109 //剔除字符串首尾的空白字符(含空格) 110 static char *StripWhite(char *pszOrig) 111 { 112 if(NULL == pszOrig) 113 return "NUL"; 114 115 char *pszStripHead = pszOrig; 116 while(isspace(*pszStripHead)) 117 pszStripHead++; 118 119 if('\0' == *pszStripHead) 120 return pszStripHead; 121 122 char *pszStripTail = pszStripHead + strlen(pszStripHead) - 1; 123 while(pszStripTail > pszStripHead && isspace(*pszStripTail)) 124 pszStripTail--; 125 *(++pszStripTail) = '\0'; 126 127 return pszStripHead; 128 } 129 130 static char *pszLineRead = NULL; //终端输入字符串 131 static char *pszStripLine = NULL; //剔除前端空格的输入字符串 132 char *ReadCmdLine() 133 { 134 //若已分配命令行缓冲区,则将其释放 135 if(pszLineRead) 136 { 137 free(pszLineRead); 138 pszLineRead = NULL; 139 } 140 //读取用户输入的命令行 141 pszLineRead = readline(pszCmdPrompt); 142 143 //剔除命令行首尾的空白字符。若剔除后的命令不为空,则存入历史列表 144 pszStripLine = StripWhite(pszLineRead); 145 if(pszStripLine && *pszStripLine) 146 add_history(pszStripLine); 147 148 return pszStripLine; 149 } 150 151 static char *CmdGenerator(const char *pszText, int dwState) 152 { 153 static int dwListIdx = 0, dwTextLen = 0; 154 if(!dwState) 155 { 156 dwListIdx = 0; 157 dwTextLen = strlen(pszText); 158 } 159 160 //当输入字符串与命令列表中某命令部分匹配时,返回该命令字符串 161 const char *pszName = NULL; 162 while((pszName = GetCmdByIndex(dwListIdx))) 163 { 164 dwListIdx++; 165 166 if(!strncmp (pszName, pszText, dwTextLen)) 167 return strdup(pszName); 168 } 169 170 return NULL; 171 } 172 173 static char **CmdCompletion (const char *pszText, int dwStart, int dwEnd) 174 { 175 //rl_attempted_completion_over = 1; 176 char **pMatches = NULL; 177 if(0 == dwStart) { 178 // pMatches = rl_completion_matches(pszText, rl_filename_completion_function); 179 // pMatches = rl_completion_matches(pszText, rl_username_completion_function); 180 pMatches = rl_completion_matches(pszText, CmdGenerator); 181 } 182 183 return pMatches; 184 } 185 186 //初始化Tab键能补齐的Command函数 187 static void InitReadLine(void) 188 { 189 rl_attempted_completion_function = CmdCompletion; 190 } 191 192 #endif 193 194 // 自动补全后的命令字符串结尾多出一个空格,故需调用StripWhite将该空格剔除。 195 // 最后,可编写交互式调测函数如下: 196 int main(void) 197 { 198 #ifndef __READLINE_DEBUG 199 printf("Note: Macro __READLINE_DEBUG is Undefined, thus InteractiveCmd is Unavailable!!!\n\n"); 200 #else 201 printf("Note: Welcome to Interactive Command!\n"); 202 printf(" Press 'Quit'/'Exit'/'End'/'Bye'/'Q'/'E'/'B' to quit!\n\n"); 203 InitReadLine(); 204 while(1) 205 {//也可加入超时机制以免忘记退出 206 char *pszCmdLine = ReadCmdLine(); 207 if(IsUserQuitCmd(pszCmdLine)) 208 { 209 free(pszLineRead); 210 break; 211 } 212 213 ExecCmd(pszCmdLine); 214 } 215 #endif 216 217 return 0; 218 }
该函数用法类似Shell,便于定制调测命令的随机执行。命令中首个参数(本文参数唯一)支持自动补全,但参数区分大小写。
编译链接时需加载readline库和termcap(或ncurses)库。ncurses库通常使用terminfo(终端信息),少数实现会使用termcap(终端能力)。启用Readline库时,执行结果如下:
1 [wangxiaoyuan_@localhost test1]$ gcc -Wall -o ReadLine ReadLine.c -D__READLINE_DEBUG -lreadline -lncurses 2 [wangxiaoyuan_@localhost 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 [wangxiaoyuan_@localhost test1]$
不启用Readline库时,执行结果如下:
1 [wangxiaoyuan_@localhost 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 [wangxiaoyuan_@localhost test1]$ ./ReadLine 5 Note: Macro __READLINE_DEBUG is Undefined, thus InteractiveCmd is Unavailable!!!