跳转至

Readline

1 Synopsis

readline 是一个由 GNU 项目开发的库,用于提供交互式命令行编辑功能。它允许用户在命令行中编辑输入内容,支持历史记录、命令补全等功能,被广泛应用于各种交互式程序中,如 bash、Python 解释器等

2 Description

C
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
#include <stdio.h>
#include <readline/readline.h>
#include <readline/history.h>


char *readline (const char *prompt);
void add_history(const char *line)
char **rl_completion_matches(const char *text, rl_compentry_func_t *entry_func);
char **(*rl_attempted_completion_function)(const char *text, int start, int end);
// 自定义补全函数
char *command_generator(const char *text, int state);
  • readline 会从终端读取一行输入并返回,同时以 prompt 作为提示符

  • promptNULL 或空字符串,则不显示提示符

  • 返回的行是通过 malloc() 动态分配的,调用者需在使用完毕后手动释放内存

  • 返回的字符串会移除末尾的换行符,仅保留行的文本内容

  • add_history 将用户输入添加到历史命令列表中,使其可通过上/下箭头访问

  • rl_attempted_completion_functionreadline 中用于自动补全的回调函数指针,需要把它指向自定义的补全函数,当用户在 readline() 输入时按下 Tab 键,就会调用这个函数来提供补全,返回一个 char ** 的字符串数组,所有候选项,最后一项为 NULL
  • text: 用户当前要补全的单词,比如输入 he| 时,text = "he"| 表示光标)
  • start: 当前补全单词在整行输入中的起始位置
  • end: 当前补全单词在整行输入中的结束位置

  • rl_completion_matches 用于生成补全结果数组,通过调用 entry_func() 多次,直到返回 NULL,返回一个 char ** 字符串数组,用于 readline 的补全候选

  • text: 当前补全文本
  • entry_func: 每次调用返回一个候选项的函数
  • command_generator 是自定义补全函数,也就是 entry_func 指向的函数,被 rl_completion_matches() 调用多次
  • text: 用户当前要补全的单词,比如输入 he| 时,text = "he"| 表示光标)
  • state: 从 0 开始递增,表示第几次调用,state 是由 readline 库内部自动管理并传给补全生成器函数的

注意

使用上述库函数需要链接 -lreadline 库,gcc readline.c -o readline -I. -lreadline

3 Example

readline 的基础用法以及简单的命令补全

C
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <readline/readline.h>
#include <readline/history.h>

// commands list
static char *commands[] = {
  "ls",
  "cd",
  "pwd",
  "mkdir",
  "mv",
  "quit",
  NULL
};

/* 返回匹配 text 的命令 */ 
char *generic_generator(const char **items, const char *text, int state) {
  static int list_index, len;
  char *name;

  // 第一次调用时初始化
  // state 是由 readline 库内部自动管理并传给补全生成器函数的
  if (!state) {
    list_index = 0;
    len = strlen(text);
  }

  // ​遍历目录列表去查询匹配的命令
  while ((name = (char *)items[list_index ++])) {
    if (strncmp(name, text, len) == 0)
      return strdup(name);
  }

  // 没有更多匹配项
  return NULL;
}

/* 命令生成器 */
char *command_generator(const char *text, int state) {
  return generic_generator((const char **)commands, text, state);
}

/* 补全函数 */
char **command_completion(const char *text, int start, int end) {
  char **matches = NULL;

  (void)end; // 显式忽略未用参数,抑制编译器警告

  // 如果 start 为 0,表示用户正在输入命令,否则表示用户正在输入参数
  if (start == 0) {
    matches = rl_completion_matches(text, command_generator);
  }

  // 返回补全候选列表
  return matches;
}

int main() {
  // 设置补全函数
  rl_attempted_completion_function = command_completion;

  char *line;
  while ((line = readline("$ ")) != NULL) {
    if (*line) {
      add_history(line);  // 添加历史记录
      printf("%s\n", line);
    }

    if (strcmp(line, "quit") == 0) {
      free(line);  // Must to do
      break;
    }

    free(line);  // Must to do
  }

  return 0;
}

执行流程图:

flowchart TD
    A[用户在命令行输入] --> B[按下 Tab 键]
    B --> C[readline 捕获 Tab 事件]
    C --> D[调用 rl_attempted_completion_function]
    D --> E[command_completion 函数]

    subgraph 命令补全处理
        E --> F{检查 start 是否为 0?}
        F -->|是| G[调用 rl_completion_matches]
        F -->|否| T[不处理参数补全]
        G --> H[command_generator 函数]
        H --> I[generic_generator 函数]

        subgraph 生成器处理
            I --> J{state 是否为 0?}
            J -->|是| K[初始化 list_index 和 len]
            J -->|否| L[继续遍历]
            K --> M[遍历 commands 数组]
            L --> M
            M --> N{是否找到匹配?}
            N -->|是| O[返回匹配项的副本]
            N -->|否| P[返回 NULL]
        end

        O --> Q[添加到匹配列表]
        P --> R[完成匹配]
        Q --> S[返回匹配列表]
    end

    S --> U[显示补全结果]
    U --> V[用户继续输入或执行命令]