GNU Readline庫函數的應用示例

 

說明

     GNU Readline是一個跨平臺開源程序庫,提供交互式的文本編輯功能。應用程序藉助該庫函數,容許用戶編輯鍵入的命令行,並提供自動補全和命令歷史等功能。Bash(Bourne Again Shell)、GDB、ftp 和mail等程序就使用Readline庫提供其命令行界面。php

     Readline是GNU通用公共許可證版本3(GNU GPLv3)下的自由軟件。這意味着若發佈或分發的程序使用Readline庫,則該程序必須是自由軟件,並持有GPL-兼容的許可證。固然,用戶也可以使用本身的實現替代庫的行爲。html

     本文將基於若干典型的Readline庫函數,給出一個簡單的交互式調測器示例。示例代碼的運行環境以下:前端

 

 

 

一  函數介紹

     本節簡要介紹後文將要用到的幾個Readline庫函數,如用來替代fgets()的readline()函數。關於庫函數的更多描述可參考http://cnswww.cns.cwru.edu/php/chet/readline/rltop.html數組

     readline()函數的ANSI C聲明以下:函數

char *readline(const char *prompt);ui

     該函數打印參數prompt 所指的提示字符串,而後讀取並返回用戶輸入的單行文本(剔除換行符)。若prompt 爲空指針或指向空字符串,則不顯示任何提示。若還沒有讀取到字符就遇到EOF,則該函數返回NULL;不然返回由malloc()分配的命令行內存,故調用結束後應經過free()顯式地釋放內存spa

     讀取命令行後,可調用add_history()函數將該行存入命令行歷史列表中,以便後續重取。命令行

void add_history(const char *string);3d

     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"!\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 [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!!!
相關文章
相關標籤/搜索