• 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_over设置为非零值,则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"!
    "); 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('' == *pszStripHead)
     33         return pszStripHead;
     34 
     35     char *pszStripTail = pszStripHead + strlen(pszStripHead) - 1;
     36     while(pszStripTail > pszStripHead && isspace(*pszStripTail))
     37         pszStripTail--;
     38     *(++pszStripTail) = '';
     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!!!
    
    ");
     5 #else
     6     printf("Note: Welcome to Interactive Command!
    ");
     7     printf("      Press 'Quit'/'Exit'/'End'/'Bye'/'Q'/'E'/'B' to quit!
    
    ");
     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 [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!!!
  • 相关阅读:
    hdu5360 Hiking(水题)
    hdu5348 MZL's endless loop(欧拉回路)
    hdu5351 MZL's Border(规律题,java)
    hdu5347 MZL's chemistry(打表)
    hdu5344 MZL's xor(水题)
    hdu5338 ZZX and Permutations(贪心、线段树)
    hdu 5325 Crazy Bobo (树形dp)
    hdu5323 Solve this interesting problem(爆搜)
    hdu5322 Hope(dp)
    Lightoj1009 Back to Underworld(带权并查集)
  • 原文地址:https://www.cnblogs.com/clover-toeic/p/3892688.html
Copyright © 2020-2023  润新知