readline库的安装和使用,实现shell命令自动补全

本文介绍了如何在自定义shell中实现命令行自动补全功能。作者从安装readline库开始,逐步解决安装过程中的问题,并最终成功为自己的shell添加了自动补全功能。

自己实现一个简单shell 的时候,想要实现命令行自动补全功能,于是上网查了查资料,发现多数都提到了

readline( ) 库,经过仔细了解后,发现其实bash 的底层实现也是调用了这个库,我的系统是 fedora 21 并没有安装这个库,且man page 中也没有相关信息

那我们直接尝试安装。

              首先平直觉 yum install readline 

              但是得到了这样的信息:

                    

提示我readline软件包已经安装。。。。。。。但是/usr/include下根本没有<readline.h>这个库好吗?于是又去查了查资料,

发现需要安装libreadline 但是由于版本问题又折腾了半天。

redhat 系列下这个软件包叫  readline-devel     ubuntu 下叫readline-dev    细分又分为libreadline5-dev   和 libreadline6-dev

                      

于是安装yum install readline-devel  成功

在/usr/include 下已经有了<readline>

                       

开始引用头文件

如图已经引用OK ,开始编译吧,迫不及待了。。。。。。。。。。。

按这个库已经花费一天半时间

这是什么鬼,是不是Makefile 有问题? 赶紧上Makefile\

我已经链接了readline 这样?????????????????????????????????

  于是有开始查资料发现了这个

 ldconfig -p | grep readline

这是一个库文件所以需要查看一下是否有这个库,如果是一个动态库那么就需要在链接的时候将这个动态库加入。

发现这就是一个动态库(不幸被我猜中)

然后只要加上这个库OK 

                                  然后make OK !

                           我的my_shell 已经有自动补全功能了。

<think>我们正在实现一个支持命令自动补全的简单shell(myshell)。根据用户需求,我们需要在C语言实现类似shell命令自动补全功能。在Linux环境下,我们可以使用GNU readline实现这个功能,它提供了丰富的行编辑功能,包括自动补全、历史记录等。 根据引用[2]中的Makefile,我们已经链接了`-lreadline -lhistory`,说明我们将使用readline实现自动补全的基本思路: 1. 使用readline替换我们之前使用的fgets等输入函数。 2. 设置补全函数,告诉readline在用户按下Tab键时如何生成补全建议。 3. 我们可以为特定的命令(如内置命令)设置补全,也可以为文件名补全(这是readline默认支持的)或其他自定义补全。 步骤: 1. 在代码中包含readline头文件。 2. 初始化时,可以设置补全函数(通过rl_attempted_completion_function变量)。 3. 编写补全生成函数,该函数在用户按下Tab键时被调用,返回一个字符串数组(以NULL结尾)作为补全建议。 4. 在main循环中,使用readline函数获取输入。 注意:readline会自动处理Tab键,并显示补全列表。如果只有一个补全选项,它会自动补全。 示例代码框架: ```c #include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #include <readline/readline.h> #include <readline/history.h> // 定义我们支持的内置命令 char *builtin_commands[] = { "cd", "exit", "help", "pwd", "setenv", "unsetenv", "history", "fg", "bg", NULL }; // 补全生成函数 char *command_generator(const char *text, int state); char **custom_completion(const char *text, int start, int end); // 补全函数:当用户按下Tab键时,这个函数会被调用 char **custom_completion(const char *text, int start, int end) { // 如果当前在行首(即start为0),则我们进行命令补全 if (start == 0) { return rl_completion_matches(text, command_generator); } // 否则,我们使用默认的文件名补全(也可以自定义其他补全) return NULL; } // 命令生成器:用于迭代匹配的命令 char *command_generator(const char *text, int state) { static int list_index, len; char *name; // 第一次调用时,初始化 if (!state) { list_index = 0; len = strlen(text); } // 遍历内置命令列表,寻找匹配项 while ((name = builtin_commands[list_index++])) { if (strncmp(name, text, len) == 0) { return strdup(name); } } // 没有更多匹配项 return NULL; } int main() { // 设置补全函数 rl_attempted_completion_function = custom_completion; while (1) { char *input = readline("myshell> "); if (!input) { break; // 遇到EOF(如Ctrl+D) } // 处理输入的命令(这里省略具体处理过程) // 将输入添加到历史记录中 if (strlen(input) > 0) { add_history(input); } free(input); } return 0; } ``` 说明: 1. `custom_completion`函数是补全的入口,当用户按下Tab时,readline会调用这个函数。在这个函数中,我们判断当前光标位置(start)是否在行首(即命令开头),如果是则调用命令补全,否则使用默认的文件名补全(这里我们返回NULL,readline使用默认的文件名补全)。 2. `command_generator`函数用于生成匹配的命令列表。它通过内置命令数组进行匹配,返回与当前输入文本匹配的命令。注意:该函数被多次调用,每次调用返回一个匹配项,直到没有更多匹配项返回NULL。 3. 在main函数中,我们使用`readline`函数获取用户输入,它会自动处理行编辑Tab补全。 4. 我们使用`add_history`将输入的命令添加到历史记录中,这样用户可以使用上下箭头来浏览历史命令。 注意:上述代码只实现了内置命令的补全,并没有处理命令参数(如cd命令后面可能需要目录补全)。如果需要更复杂的补全(如命令参数的补全),我们可以在`custom_completion`函数中根据当前命令(通过解析行)来设置不同的补全方式。 例如,我们可以解析输入行,如果第一个单词是"cd",那么我们可以设置使用文件名补全(目录补全): ```c char **custom_completion(const char *text, int start, int end) { // 获取当前行的所有单词 char **words = rl_completion_matches(text, rl_filename_completion_function); // 如果当前行有单词,并且第一个单词是"cd",则使用文件名补全 if (words && words[0]) { if (strcmp(words[0], "cd") == 0) { // 注意:这里需要释放之前words的内存吗?实际上rl_completion_matches返回的是需要释放的,但我们不能在此释放,因为后面还要用。 // 所以这里我们直接使用文件名补全 return rl_completion_matches(text, rl_filename_completion_function); } } // 否则,如果是在行首,则使用命令补全 if (start == 0) { return rl_completion_matches(text, command_generator); } // 其他情况使用默认的文件名补全 return rl_completion_matches(text, rl_filename_completion_function); } ``` 但是注意:上面的代码只是一个思路,实际上在`custom_completion`函数中,我们不应该多次调用`rl_completion_matches`,因为它会分配内存,而且我们无法在此函数中释放(因为readline会负责释放)。更合理的做法是根据不同的情况返回不同的生成器函数。 更复杂的做法是:解析当前行,判断第一个单词(即命令)是什么,然后针对该命令的参数进行补全。例如,对于cd命令,我们只希望补全目录名,而其他命令可能补全文件名。我们可以这样: ```c char **custom_completion(const char *text, int start, int end) { char *line = rl_line_buffer; // 当前行的所有内容 int point = rl_point; // 当前光标位置 // 解析当前行,获取当前命令的第一个单词 // 我们可以使用rl_completion_word_break_characters定义的单词分隔符来解析 // 如果当前行没有单词,则使用命令补全 if (start == 0) { return rl_completion_matches(text, command_generator); } // 获取当前行的单词列表(以空格分隔) char *token, *saveptr; char *line_copy = strdup(line); token = strtok_r(line_copy, " ", &saveptr); if (token == NULL) { free(line_copy); return NULL; } // 检查第一个单词(命令) if (strcmp(token, "cd") == 0) { free(line_copy); // 对于cd命令,我们使用目录名补全 return rl_completion_matches(text, rl_filename_completion_function); } // 其他命令,我们可以根据情况处理,比如setenv命令可能需要环境变量名补全等 // 这里我们默认使用文件名补全 free(line_copy); return rl_completion_matches(text, rl_filename_completion_function); } ``` 注意:上面的代码只是一个示例,实际应用中可能需要更复杂的解析,并且要注意内存管理(如strdup分配的内存需要释放)。 另外,我们也可以使用readline提供的更高级的补全机制,比如为不同的命令注册不同的补全函数。但为了简单起见,我们这里只提供一个基本的框架。 最后,编译程序时需要链接readline,如引用[2]中的Makefile所示。 这样,我们就为myshell实现命令自动补全功能。 §§相关问题§§ 1. 如何扩展这个自动补全功能以支持命令参数(如特定命令的选项)? 2. 在自定义补全函数中,如何避免补全非目录文件(例如cd命令只补全目录)? 3. 如何利用readline实现更高级的补全功能,比如子命令补全?</think>### 在C语言实现myshell命令自动补全 在C语言实现类似Shell命令自动补全功能,主要依赖**GNU readline**。以下是具体实现步骤代码示例: #### 1. 核心实现步骤 1. **初始化readline** 设置`rl_attempted_completion_function`指定自定义补全函数 2. **定义命令列表** 列出所有支持补全的命令(如内置命令外部命令) 3. **实现补全生成器** 使用`rl_completion_matches`匹配用户输入的命令前缀 4. **处理特殊命令** 如`cd`命令只补全目录(通过`rl_filename_completion_function`) #### 2. 完整代码示例 ```c #include <stdio.h> #include <stdlib.h> #include <string.h> #include <readline/readline.h> #include <readline/history.h> // 支持补全的命令列表 char *command_list[] = { "cd", "exit", "ls", "pwd", "cat", "grep", "mkdir", "rm", "cp", "mv", "ps", "top", "env", "export", "unset", "help", "fg", "bg", "jobs", "history", "clear", "vim", NULL }; // 命令补全生成器 char *command_generator(const char *text, int state) { static int list_index, len; char *name; if (!state) { // 首次调用初始化 list_index = 0; len = strlen(text); } // 遍历命令列表寻找匹配项 while ((name = command_list[list_index++])) { if (strncmp(name, text, len) == 0) { return strdup(name); // 返回匹配的命令副本 } } return NULL; // 无更多匹配 } // 补全处理函数 char **custom_completion(const char *text, int start, int end) { if (start == 0) { // 行首命令补全 return rl_completion_matches(text, command_generator); } else { // 参数补全(如cd的目录) return rl_completion_matches(text, rl_filename_completion_function); } } int main() { // 设置补全函数 rl_attempted_completion_function = custom_completion; // 设置TAB键补全 rl_bind_key('\t', rl_complete); while (1) { char *input = readline("myshell> "); if (!input) break; // 处理Ctrl+D // 执行命令逻辑(此处省略具体实现) printf("执行命令: %s\n", input); // 添加到历史记录 if (strlen(input) > 0) add_history(input); free(input); } return 0; } ``` #### 3. 关键配置说明 1. **编译选项** 在Makefile中添加readline链接: ```makefile CC = gcc CFLAGS = -g -Wall LDLIBS = -lreadline -lhistory myshell: myshell.c $(CC) $(CFLAGS) -o $@ $^ $(LDLIBS) ``` 2. **特殊命令处理** 在`custom_completion()`中: - 行首输入(`start == 0`):使用命令补全 - 非行首输入:使用文件名补全(如`cd`后的目录) 3. **定制补全行为** - 通过`rl_completion_append_character = '\0'`禁止自动添加空格 - 使用`rl_bind_key('\t', rl_complete)`绑定TAB键 #### 4. 效果说明 1. 输入`c` + TAB 显示: ```bash cd cp cat clear ``` 2. 输入`cd D` + TAB 补全为`cd Documents/`(目录存在时) 3. 上下键可浏览历史命令 > 注意:此实现需要readline安装:`sudo apt-get install libreadline-dev`)。实际项目中需结合命令解析、进程控制等完整shell功能[^1][^3][^4]。
评论 1
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符  | 博主筛选后可见
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值