如何在CentOS下用C语言实现stdin对箭头键(↑↓←→)的正确支持?
解决CentOS下C语言命令行工具箭头键乱码问题
你在CentOS上用C语言开发的命令行工具,运行时按下箭头键会出现^[[D这类控制字符,没法正常移动光标修正输入。这是因为终端默认处于规范模式(canonical mode),会把箭头、退格这类特殊按键转换成可见的控制序列,而fgets只能读取完整的输入行,不支持逐字符的输入编辑和光标控制。
下面给两种解决办法:
方法一:手动修改终端模式并处理控制字符
这种方法需要自己操作终端配置,识别箭头键的转义序列,实现光标移动逻辑:
- 保存终端原有配置,切换到非规范模式,关闭自动回显,这样可以逐字符读取输入。
- 识别箭头键的转义序列:左箭头是
ESC [ D(ASCII码27、91、68),右箭头是ESC [ C,上箭头ESC [ A,下箭头ESC [ B。 - 实现光标移动、字符插入/删除的基础编辑逻辑。
- 程序退出前必须恢复终端原配置,避免终端出现异常。
修改后的代码示例:
// client.c #include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #include <termios.h> #include <ctype.h> #define BUF_SIZE 128 // 保存终端原配置 struct termios old_tio; // 恢复终端配置 void restore_terminal() { tcsetattr(STDIN_FILENO, TCSANOW, &old_tio); } int main(int argc, char *argv[]) { char command[BUF_SIZE] = {0}; int pos = 0; // 当前光标位置 struct termios new_tio; // 获取终端原配置 tcgetattr(STDIN_FILENO, &old_tio); atexit(restore_terminal); // 程序退出时自动恢复 // 设置新的终端模式:非规范模式,关闭回显 new_tio = old_tio; new_tio.c_lflag &= ~(ICANON | ECHO); new_tio.c_cc[VMIN] = 1; // 至少读取1个字符 new_tio.c_cc[VTIME] = 0; tcsetattr(STDIN_FILENO, TCSANOW, &new_tio); while (1) { // 打印提示符和当前输入内容 printf("\rcli > %s", command); fflush(stdout); char c; read(STDIN_FILENO, &c, 1); if (c == '\n') { // 处理回车 printf("\n"); if (pos > 0) { if (strcmp(command, "exit") == 0) { break; } printf("do something ... %s\n", command); } // 清空输入 memset(command, 0, BUF_SIZE); pos = 0; } else if (c == 127) { // 退格键 if (pos > 0) { pos--; memmove(&command[pos], &command[pos+1], BUF_SIZE - pos - 1); command[BUF_SIZE-1] = 0; } } else if (c == 27) { // ESC键,处理箭头键转义序列 char seq[2]; read(STDIN_FILENO, seq, 2); if (seq[0] == '[') { switch(seq[1]) { case 'D': // 左箭头 if (pos > 0) pos--; break; case 'C': // 右箭头 if (pos < strlen(command)) pos++; break; // 可扩展上/下箭头处理历史命令 } } } else if (isprint(c) && pos < BUF_SIZE - 1) { // 可打印字符,插入到当前光标位置 memmove(&command[pos+1], &command[pos], BUF_SIZE - pos - 1); command[pos] = c; pos++; } } return 0; }
方法二:使用GNU Readline库(推荐)
手动实现编辑逻辑很繁琐,GNU Readline库已经封装了命令行编辑、光标移动、历史记录等功能,用起来更简单。
步骤:
- 安装依赖:在CentOS上执行
yum install readline-devel - 修改代码使用readline函数:
// client.c #include <stdio.h> #include <stdlib.h> #include <string.h> #include <readline/readline.h> #include <readline/history.h> int main(int argc, char *argv[]) { char *command; // 启用历史记录 using_history(); while ((command = readline("cli > ")) != NULL) { if (strlen(command) == 0) { free(command); continue; } if (strcmp(command, "exit") == 0) { free(command); break; } // 添加到历史记录 add_history(command); printf("do something ... %s\n", command); free(command); } return 0; }
编译命令:
编译时需要链接readline库:
gcc client.c -o client -lreadline
这个版本不仅解决了箭头键的问题,还支持上下箭头查看历史命令,体验和系统自带的shell一致。
内容的提问来源于stack exchange,提问作者czg
相关产品推荐
相关产品推荐

