如何在类 Unix 环境下无需等待回车即可获取来自键盘的输入?


◆ 问题

在类 Unix 操作系统中,C++ 程序使用 stdio 或 iostream 程序库接收键盘输入时,需等待回车键按下后才能接收到输入内容。如何能在无需等待回车即可获取来自键盘的输入?

◆ 解法

通过 termios 程序库中与终端关联的 termios 结构和 tcgetattr、tcsetattr 函数,改变终端编辑模式为非规范输入且不回显,即可实现“无需等待回车即可获取来自键盘的输入”的功能。

◆ 示例

以下代码片段展示了此实现方案,(how_to_get_input_without_enter.cpp)

#include 
#include           // #1


int hit_key() {
    termios oldattr;
    tcgetattr(0, &oldattr);      // #2

    termios newattr = oldattr;      // #3
    newattr.c_lflag &= ~(ICANON | ECHO);      // #4
    tcsetattr(0, TCSANOW, &newattr);

    int code = getchar();

    tcsetattr(0, TCSANOW, &oldattr);     // #5

    return code;
}

首先需要包含 termios.h 程序库(#1),该文件定义了与终端关联的 termios 结构和 tcgetattr、tcsetattr 函数。使用 tcgetattr 函数获取当前终端的属性,并保存在 termios 结构的变量中(#2)。为了能将终端属性恢复成原有值,先用原有终端属性复制出一份临时属性(#3)。改变终端编辑模式的关键,在于 termios.c_lflag 字段。将临时属性的 c_lflag 字段改变为非规范输入且不回显(#4),并设置终端属性为新值。应用结束后,将终端属性恢复成原有值(#5)。

◆ 验证

笔者分别在 macOS Mojave(版本 10.14.6)和 Raspbian Stretch(版本 9.4)两个操作系统的原生终端内,用 87 键键盘输入 ASCII 字符集进行了验证。

编译代码成功后运行可执行文件。对于十进制 ASCII 码为 32 ~ 127 的可打印字符可直接按键输入,而对于 ASCII 码为 0 ~ 31 的控制字符按如下组合键输入,

十进制 字符 macOS Mojave 组合键 Raspbian Stretch 组合键
0 ^@ Ctrl-@ Ctrl-@
1 ^A Ctrl-A Ctrl-A
2 ^B Ctrl-B Ctrl-B
3 ^C Ctrl-V Ctrl-C (未验证)
4 ^D Ctrl-D Ctrl-D
5 ^E Ctrl-E Ctrl-E
6 ^F Ctrl-F Ctrl-F
7 ^G Ctrl-G Ctrl-G
8 ^H Ctrl-H Ctrl-H
9 Tab, ^I Tab, Ctrl-I Tab, Ctrl-I
10 ^J Ctrl-J Ctrl-J
11 ^K Ctrl-K Ctrl-K
12 ^L Ctrl-L Ctrl-L
13 ^M Ctrl-V Ctrl-M (未验证)
14 ^N Ctrl-N Ctrl-N
15 ^O Ctrl-V Ctrl-O Ctrl-O
16 ^P Ctrl-P Ctrl-P
17 ^Q Ctrl-V Ctrl-Q (未验证)
18 ^R Ctrl-R Ctrl-R
19 ^S Ctrl-V Ctrl-S (未验证)
20 ^T Ctrl-T Ctrl-T
21 ^U Ctrl-U Ctrl-U
22 ^V Ctrl-V Ctrl-V Ctrl-V
23 ^W Ctrl-W Ctrl-W
24 ^X Ctrl-X Ctrl-X
25 ^Y Ctrl-V Ctrl-Y Ctrl-Y
26 ^Z Ctrl-V Ctrl-Z (未验证)
27 ESC, ^[ Ctrl-[ ESC, Ctrl-[
28 ^\ Ctrl-V Ctrl-\ (未验证)
29 ^] Ctrl-] Ctrl-]
30 ^^ Ctrl-^ Ctrl-^
31 ^- Ctrl-- Ctrl--

在 Raspbian Stretch 上的测试中存在”(未验证)“的 ASCII 码,是因为该示例代码无法通过键盘输入对应的控制符。

以下是运行过程中的部分输出,

macos:15691156 green$ ./a.out 

Please hit a key (Ctrl-C to quit).

......

Your input is A, its ASCII code is 65.

Your input is [, its ASCII code is 91.

Your input is B, its ASCII code is 66.

Your input is 5, its ASCII code is 53.

Your input is ~, its ASCII code is 126.

......

◆ 最后

完整示例代码请参考 [github] cnblogs/15691156 。

写作过程中,笔者参考了 Linux下C++/C终端获取键盘事件/termios结构详细解释 和 LINUX 使用tcgetattr与tcsetattr函数控制终端。致 Liuqz2009 和 凡人只做一事 两位作者。