Chinaunix首页 | 论坛 | 博客
  • 博客访问: 758262
  • 博文数量: 144
  • 博客积分: 0
  • 博客等级: 民兵
  • 技术积分: 1150
  • 用 户 组: 普通用户
  • 注册时间: 2014-03-17 14:32
个人简介

小公司研发总监,既当司令也当兵!

文章分类

全部博文(144)

分类: LINUX

2015-05-25 01:46:59

readline库实现命令行自动补全

   Linux下应用程序可能需要交互式输入命令,但普通的标准IO进行命令输入显得有些呆板,人性化不足。本文讲述使用libreadline库,实现类似sh的交换终端:支持命令自动补全,支持历史命令等。

    part1: readline安装
    (1) 下载readline源码:
    (2) 解压后, 在源码目录依次执行 ./configure, make, sudo make install 完成安装

    part2:readline使用举例
点击(此处)折叠或打开
  1. #include <stdlib.h>
  2. #include <stdio.h> //注意,readline.h中可能需要调用标准IO库的内容,所以stdio.h必须在readline.h之前被包含
  3. #include <readline/readline.h>
  4. #include <readline/history.h>


  5. /*
  6. * 真正的命令执行函数
  7. * 测试时,被定义为桩函数了
  8. */
  9. int com_list(char *para)
  10. {
  11.     printf("do com_list:%s\n", para);
  12.     return 0;
  13. }
  14.     
  15. int com_view(char *para)
  16. {
  17.     printf("do com_view:%s\n", para);
  18.     return 0;
  19. }

  20. int com_rename(char *para)
  21. {
  22.     printf("do com_rename:%s\n", para);
  23.     return 0;
  24. }
  25. int com_stat(char *para)
  26. {
  27.     printf("do com_stat:%s\n", para);
  28.     return 0;
  29. }

  30. int com_pwd(char *para)
  31. {
  32.     printf("do com_pwd:%s\n", para);
  33.     return 0;
  34. }
  35. int com_delete(char *para)
  36. {
  37.     printf("do com_delete:%s\n", para);
  38.     return 0;
  39. }
  40. int com_help(char *para)
  41. {
  42.     printf("do com_help:%s\n", para);
  43.     return 0;
  44. }

  45. int com_cd(char *para)
  46. {
  47.     printf("do com_cd:%s\n", para);
  48.     return 0;
  49. }
  50. int com_quit(char *para)
  51. {
  52.     printf("do com_quit:%s\n", para);
  53.     exit(0);
  54. }


  55. /*
  56. * A structure which contains information on the commands this program
  57. * can understand.
  58. */
  59. typedef struct {
  60.     char *name;            /* User printable name of the function. */
  61.     rl_icpfunc_t *func;        /* Function to call to do the job. */
  62.     char *doc;            /* Documentation for this function. */
  63. } COMMAND;

  64. COMMAND commands[] = {
  65.   { "cd", com_cd, "Change to directory DIR" },
  66.   { "delete", com_delete, "Delete FILE" },
  67.   { "help", com_help, "Display this text" },
  68.   { "?", com_help, "Synonym for `help'" },
  69.   { "list", com_list, "List files in DIR" },
  70.   { "ls", com_list, "Synonym for `list'" },
  71.   { "pwd", com_pwd, "Print the current working directory" },
  72.   { "quit", com_quit, "Quit using Fileman" },
  73.   { "rename", com_rename, "Rename FILE to NEWNAME" },
  74.   { "stat", com_stat, "Print out statistics on FILE" },
  75.   { "view", com_view, "View the contents of FILE" },
  76.   { (char *)NULL, (rl_icpfunc_t *)NULL, (char *)NULL }
  77. };


  78. char* dupstr(char *s)
  79. {
  80.   char *r;

  81.   r = malloc (strlen (s) + 1);
  82.   strcpy(r, s);
  83.   return (r);
  84. }

  85. // clear up white spaces
  86. char* stripwhite (char *string)
  87. {
  88.     register char *s, *t;
  89.     
  90.     for (s = string; whitespace (*s); s++)
  91.         ;
  92.     
  93.     if (*s == 0)
  94.         return (s);

  95.     t = s + strlen (s) - 1;
  96.     while (t > s && whitespace (*t))
  97.         t--;

  98.     *++t = '\0';

  99.     return s;
  100. }


  101. /*
  102. * Look up NAME as the name of a command, and return a pointer to that
  103. * command. Return a NULL pointer if NAME isn't a command name.
  104. */
  105. COMMAND *find_command (char *name)
  106. {
  107.     register int i;
  108.     
  109.     for (i = 0; commands[i].name; i++)
  110.         if (strcmp (name, commands[i].name) == 0)
  111.             return (&commands[i]);

  112.     return ((COMMAND *)NULL);
  113. }

  114. /* Execute a command line. */
  115. int execute_line (char *line)
  116. {
  117.     register int i;
  118.     COMMAND *command;
  119.     char *word;

  120.     /* Isolate the command word. */
  121.     i = 0;
  122.     while (line[i] && whitespace (line[i]))
  123.         i++;
  124.     word = line + i;

  125.     while (line[i] && !whitespace (line[i]))
  126.         i++;

  127.     if (line[i])
  128.         line[i++] = '\0';

  129.     command = find_command (word);

  130.     if (!command)
  131.     {
  132.         fprintf (stderr, "%s: No such command for FileMan.\n", word);
  133.         return (-1);
  134.     }

  135.     /* Get argument to command, if any. */
  136.     while (whitespace (line[i]))
  137.         i++;

  138.     word = line + i;

  139.     /* Call the function. */
  140.     return ((*(command->func)) (word));
  141. }

  142. /*
  143. * Generator function for command completion. STATE lets us know whether
  144. * to start from scratch; without any state (i.e. STATE == 0), then we
  145. * start at the top of the list.
  146. */
  147. char* command_generator (const char *text, int state)
  148. {
  149.     static int list_index, len;
  150.     char *name;

  151.     /*
  152.     * If this is a new word to complete, initialize now. This includes
  153.     * saving the length of TEXT for efficiency, and initializing the index
  154.     * variable to 0.
  155.     */
  156.     if (!state)
  157.     {
  158.          list_index = 0;
  159.         len = strlen (text);
  160.     }

  161.     /* Return the next name which partially matches from the command list. */
  162.     while (name = commands[list_index].name)
  163.     {
  164.          list_index++;
  165.     
  166.          if (strncmp (name, text, len) == 0)
  167.              return (dupstr(name));
  168.     }

  169.     /* If no names matched, then return NULL. */
  170.     return ((char *)NULL);
  171. }

  172. /*
  173. * Attempt to complete on the contents of TEXT. START and END bound the
  174. * region of rl_line_buffer that contains the word to complete. TEXT is
  175. * the word to complete. We can use the entire contents of rl_line_buffer
  176. * in case we want to do some simple parsing. Return the array of matches,
  177. * or NULL if there aren't any.
  178. */
  179. char** fileman_completion (const char *text, int start, int end)
  180. {
  181.     char **matches;

  182.     matches = (char **)NULL;

  183.     /*
  184.     * If this word is at the start of the line, then it is a command
  185.     * to complete. Otherwise it is the name of a file in the current
  186.     * directory.
  187.     */
  188.     if (start == 0)
  189.         matches = rl_completion_matches (text, command_generator);

  190.     return (matches);
  191. }


  192. /*
  193. * Tell the GNU Readline library how to complete. We want to try to complete
  194. * on command names if this is the first word in the line, or on filenames
  195. * if not.
  196. */
  197. void initialize_readline ()
  198. {
  199.     /* Allow conditional parsing of the ~/.inputrc file. */
  200.     rl_readline_name = ">";

  201.     /* Tell the completer that we want a crack first. */
  202.     rl_attempted_completion_function = fileman_completion;
  203. }




  204. int main (int argc, char **argv)
  205. {
  206.     char *line, *s;
  207.     
  208.     initialize_readline();    /* Bind our completer. */


  209.     /* Loop reading and executing lines until the user quits. */
  210.     for ( ; ;)
  211.     {
  212.         line = readline (">: ");

  213.         if (!line)
  214.             break;

  215.         /*
  216.         * Remove leading and trailing whitespace from the line.
  217.         * Then, if there is anything left, add it to the history list
  218.         * and execute it.
  219.         */
  220.          s = stripwhite (line);
  221.          if (*s)
  222.          {
  223.              add_history(s);
  224.              execute_line(s);
  225.          }

  226.         free(line);
  227.     }
  228.     exit(0);
  229. }
    注意,编译的时候需要连接readline库,例如:gcc  -o  irlt  iReadlineTest.c  -lreadline   

    part3: readline下的IO复用
    

   但是如果我们有多个 IO 要处理,比如既要从一个网络 IO 中读数据,又要从标准输入中读取命令,上面的方法就不合适了。为了解决这个问题,我们需要自己用 select 函数监控这两个 IO,当它们可读的时候通知这两个模块中的输入函数。形象地说,就是把数据“喂给”这两个模块。这样的模式需要 readline 提供“被动喂给”的工作方式。这种工作方式在 readline 中已有实现。首先,我们需要往 readline 注册回调函数,当 readline 读取到一行后,这个回调函数将被调用:

  rl_callback_handler_install ("prompt> ", handle_command);

接下去,在主事件循环中,我们需要调用 rl_callback_read_char() 通知 readline 去读取一个字符。

下面给一个例子:

点击(此处)折叠或打开

  1. static void handle_command (char *line)
  2. {
  3.     ...
  4. }
  5.  
  6. int
  7. main (int argc, char **argv)
  8. {
  9.     int netfd
  10.     fd_set allfd;
  11.     int maxfd;
  12.  
  13.     netfd = connect_to_server ();
  14.  
  15.  
  16.     FD_ZERO (&allfd);
  17.     FD_SET (fileno(stdin), &allfd);
  18.     FD_SET (netfd, &allfd);
  19.     maxfd = netfd;
  20.  
  21.     rl_callback_handler_install ("ccnet> ", handle_command);
  22.  
  23.     while (1) {
  24.         fd_set rfds;
  25.         int retval;
  26.  
  27.         rfds = allfd;
  28.  
  29.         retval = select (maxfd + 1, &rfds, NULL, NULL, NULL);
  30.         if (retval < 0)
  31.             perror ("select");
  32.  
  33.         if (FD_ISSET(0, &rfds))
  34.             rl_callback_read_char();
  35.  
  36.         if (FD_ISSET(netfd, &rfds))
  37.             read_from_network (netfd);
  38.     }
  39. }

关于readline更多的使用请自行阅读readline的man文件。




阅读(7455) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~