接下来我们来尝试获取用户输入:

1
2
3
4
5
6
#include<unistd.h>
int main() {
char c;
while(read(SRDIN_FILENO, &c, 1) == 1);
return 0;
}

这里面的 read 函数和 SRDIN_FILENO 都在 unistd.h 中,我们用 read 从标准输入也就是终端读取一个字节,然后放到变量 c 所在的地址中,直到再也没有输入了

默认情况下我们终端输入的模式是:canonical mode 它只有在我们摁下回车键的时候才会把我们在键盘输入的内容给我们的程序,这样方便我们编辑好了再给程序,但是对于文本编辑器来说我们希望是摁下一个键之后马上发给程序

通过 q 退出

添加一个功能:当用户输入 q 的时候退出程序

1
2
3
4
5
6
#include<unistd.h>
int main() {
char c;
while(read(SRDIN_FILENO, &c, 1) == 1 && c != 'q');
return 0;//加了个c != 'q'
}

输入一堆东西都没问题,但是当输入 q 的时候摁下回车就退出了

image.png
image.png

关闭回显

我们可以通过下面的方法来改变终端属性
(1)使用 tcgetattr() 将当前属性读取到声明的结构中
(2)修改该结构
(3)使用 tcsetattr() 传递修改后的结构,更改终端属性

image.png
image.png

更改这个值,让他变成不回显

image.png
image.png
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#include<unistd.h>
#include<termios.h>
void enableRawMode(){
struct termios raw;//先声明一个当前终端属性的结构体
tcgetattr(STDIN_FILENO, &raw);//通过tcgetattr读入
raw.c_lflag &= ~(ECHO);
tcsetattr(STDIN_FILENO,TCSAFLUSH,&raw);//通过tcsetattr写入
//TCSAFLUSH:发送了所有输出后更改才发生,在更改发生时未读的所有输入数据被删除(Flush)
}
}
int main() {
enableRawMode();
char c;
while(read(SRDIN_FILENO, &c, 1) == 1 && c != 'q');
return 0;
}

这样输入的时候就不会回显了,然而出之后也不会,但是通过用 reset 将终端机回复至原始状态,就好了

退出时恢复

让我们对用户有好一点,在程序退出的时候自动恢复输入回显(我怎么感觉再着来一遍那个函数就可以了呐?)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
#include <termios.h>
#include <unistd.h>
#include <stdlib.h>
struct termios orig_termios;
//保存一个原始的
void disableRawMode(){
tcsetattr(STDIN_FILENO, TCSAFLUSH, &orig_termios);
}//恢复成原始的结构体

void enableRawMode(){
tcgetattr(STDIN_FILENO,&orig_termios);
atexit(disableRawMode);
struct termios raw = orig_termios;
raw.c_lflag &= ~(ECHO);
tcsetattr(STDIN_FILENO, TCSAFLUSH, &raw);
}
int main(){
enableRawMode();
char c;
while(read(STDIN_FILENO, &c, 1) == 1 && c != 'q');
return 0;
}

atexit() 是 <stdlib.h> 中定义的,当程序退出的时候会自动调用他里面的那个函数

关掉 canonical mode

关掉之后就是逐字节读取,而不是逐行读取,也就是说这样就不用输入然后回车了
有个 ICANON 标志,raw.c_lflag &= ~(ECHO | ICANON); 就可以

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
#include <termios.h>
#include <unistd.h>
#include <stdlib.h>
struct termios orig_termios;
//保存一个原始的
void disableRawMode(){
tcsetattr(STDIN_FILENO, TCSAFLUSH, &orig_termios);
}//恢复成原始的结构体

void enableRawMode(){
tcgetattr(STDIN_FILENO,&orig_termios);
atexit(disableRawMode);
struct termios raw = orig_termios;
raw.c_lflag &= ~(ECHO | ICANON);
tcsetattr(STDIN_FILENO, TCSAFLUSH, &raw);
}
int main(){
enableRawMode();
char c;
while(read(STDIN_FILENO, &c, 1) == 1 && c != 'q');
return 0;
}

这时候摁下 q 可就直接退出了,不用回车了,而且退出之后也会自动换回原来的输入有回显模式

Display keypresses

显示摁键

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
#include <ctype.h>
#include <stdio.h>
#include <termios.h>
#include <unistd.h>
#include <stdlib.h>
struct termios orig_termios;

void disableRawMode(){
tcsetattr(STDIN_FILENO, TCSAFLUSH, &orig_termios);
}

void enableRawMode(){
tcgetattr(STDIN_FILENO,&orig_termios);
atexit(disableRawMode);
struct termios raw = orig_termios;
raw.c_lflag &= ~(ECHO | ICANON);
tcsetattr(STDIN_FILENO, TCSAFLUSH, &raw);
}
int main(){
enableRawMode();
char c;
while(read(STDIN_FILENO, &c, 1) == 1 && c != 'q'){
if(iscntrl(c)){
printf("%d\n",c);//是控制字符的话打印ascii
} else {
printf("%d ('%c')\n",c,c);//不是控制字符的话打印对用的ascii
}
}
return 0;
}

C 库函数 void iscntrl(int c) 检查所传的字符是否是控制字符
根据标准 ASCII 字符集,控制字符的 ASCII 编码介于 0x00 (NUL) 和 0x1f (US) 之间,以及 0x7f (DEL),某些平台的特定编译器实现还可以在扩展字符集(0x7f 以上)中定义额外的控制字符

image.png
image.png

可以多尝试一些别的摁键

image.png
image.png

27 开头然后 ‘[‘ 然后是一个或两个其他字符。这称为转义序列
如果摁到 ctrl+z 或者 ctrl+y 可能会挂起程序,使用 fq 命令恢复它
如果按了 Ctrl+S 意思是要求程序停止发送输出,按 Ctrl+Q 告诉它恢复发送输出

关掉 Ctrl+C 和 Ctrl+Z

linux 默认的 ctrl+c 会发送 SIGINT 信号来终止进程
ctrl+z 会发送 SIGTSTP 来挂起进程,我们需要把这两个关掉

只需要改:ISIG 就可以啦

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
#include <ctype.h>
#include <stdio.h>
#include <termios.h>
#include <unistd.h>
#include <stdlib.h>
struct termios orig_termios;

void disableRawMode(){
tcsetattr(STDIN_FILENO, TCSAFLUSH, &orig_termios);
}

void enableRawMode(){
tcgetattr(STDIN_FILENO,&orig_termios);
atexit(disableRawMode);
struct termios raw = orig_termios;
raw.c_lflag &= ~(ECHO | ICANON | ISIG);
tcsetattr(STDIN_FILENO, TCSAFLUSH, &raw);
}
int main(){
enableRawMode();
char c;
while(read(STDIN_FILENO, &c, 1) == 1 && c != 'q'){
if(iscntrl(c)){
printf("%d\n",c);//是控制字符的话打印ascii
} else {
printf("%d ('%c')\n",c,c);//不是控制字符的话打印对用的ascii
}
}
return 0;
}

ps.之前学的那一点 git 的操作一直没有用到忘干净了,趁这个机会学习一下哈哈哈哈
想跟 github 同步起来,先在 github 建立一个仓库,然后 clone 到本地,一通编辑操作之后想要同步的时候:
1、git add 文件(可以多个空格隔开) 放到暂存区
2、git commit -m “注释” 房间进仓库
3、git push -u origin master(会要求输入 github 的用户名和密码,输入就好了哇)

命令行操作就是舒服哈哈哈哈哈

禁用 ctrl+q 和 ctrl+s

只需要更改 c_iflag 的 IXON,添加上一句
raw.c_iflag &= ~(IXON);

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
#include <ctype.h>
#include <stdio.h>
#include <termios.h>
#include <unistd.h>
#include <stdlib.h>
struct termios orig_termios;

void disableRawMode(){
tcsetattr(STDIN_FILENO, TCSAFLUSH, &orig_termios);
}

void enableRawMode(){
tcgetattr(STDIN_FILENO,&orig_termios);
atexit(disableRawMode);
struct termios raw = orig_termios;
raw.c_iflag &= ~(IXON);
raw.c_lflag &= ~(ECHO | ICANON | ISIG);
tcsetattr(STDIN_FILENO, TCSAFLUSH, &raw);
}
int main(){
enableRawMode();
char c;
while(read(STDIN_FILENO, &c, 1) == 1 && c != 'q'){
if(iscntrl(c)){
printf("%d\n",c);//是控制字符的话打印ascii
} else {
printf("%d ('%c')\n",c,c);//不是控制字符的话打印对用的ascii
}
}
return 0;
}

禁用 Ctrl+V

在 c_lflag 中加上个 IEXTEN
raw.c_lflag &= ~(ECHO | ICANON | IEXTEN | ISIG);

额,就不整个复制了

修复 Ctrl+M

现在 ctrl+m 是 10 而不是 13,而 ctrl+j 也是 10。ctrl+m 应该是 13 的
加上个 ICRNL
raw.c_iflag &= ~(ICRNL | IXON);

修复换行

\r 回到行的最左边
\n 滚到下一行

我看实现的效果是把 \n 换成用 \r\n 才能实现换行
raw.c_oflag &= ~(OPOST);

但是这样会出现这样的效果

image.png
image.png

还要再消除一下,把代码中的 \n 换成 \r\n

关一下其他的选项

这些东西关掉可能没啥效果,但是还是关掉了 BRKINT, INPCK, ISTRIP, CS8

BRKINT 打开时,中断条件将导致 SIGINT 信号发送到程序,就像按 Ctrl+C 一样
INPCK 启用了奇偶校验,这似乎不适用于现代的终端仿真器
ISTRIP 导致每个输入字节的第 8 位被剥离,这意味着它将设置为 0。这可能已经关闭
CS8 不是标志,它是一个具有多个位的位掩码,与我们要关闭的所有标志不同,我们使用按位或(|)运算符进行设置。 它将字符大小(CS)设置为每字节 8 位。 在我的系统上,它已经设置好了

read()超时

没看出来效果

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
#include <ctype.h>
#include <stdio.h>
#include <termios.h>
#include <unistd.h>
#include <stdlib.h>
struct termios orig_termios;

void disableRawMode(){
tcsetattr(STDIN_FILENO, TCSAFLUSH, &orig_termios);
}

void enableRawMode(){
tcgetattr(STDIN_FILENO,&orig_termios);
atexit(disableRawMode);
struct termios raw = orig_termios;
raw.c_iflag &= ~(BRKINT | INPCK | ISTRIP | IXON | ICRNL);
raw.c_oflag &= ~(OPOST);
raw.c_cflag |= (CS8);
raw.c_lflag &= ~(ECHO | ICANON | IEXTEN | ISIG);
raw.c_cc[VMIN] = 0;
raw.c_cc[VTIME] = 1;
tcsetattr(STDIN_FILENO, TCSAFLUSH, &raw);
}
int main(){
enableRawMode();
while(1){
char c = '\0';
read(STDIN_FILENO, &c, 1);
if(iscntrl(c)){
printf("%d\r\n",c);
} else {
printf("%d ('%c')\r\n",c,c);
}
if(c == 'q') break;
}
return 0;
}

处理错误

perror() 是 <stdio.h> 中的,exit() 来自 <stdlib.h>
大多数失败的 C 库函数都将设置全局 errno 变量以指示错误是什么。perror() 查看全局 errno 变量并为其打印描述性错误消息。 在打印错误消息之前,它还会打印给它的字符串,这是为了提供有关代码的哪一部分导致错误的上下文

1
2
3
4
void die(const char *s) {
perror(s);
exit(1);
}

在打印出错误消息后,我们以退出状态 1 退出程序,该退出状态指示失败(任何非零值也是如此)
让我们检查每个库调用是否失败,并在失败时调用 die()

errno 和 EAGAIN 来自<errno.h>
tcsetattr(),tcgetattr() 和 read() 均在失败时返回 -1,并设置 errno 值以指示错误

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
/*** includes ***/
#include <ctype.h>
#include <stdio.h>
#include <errno.h>
#include <termios.h>
#include <unistd.h>
#include <stdlib.h>

/*** data ***/
struct termios orig_termios;

/*** terminal ***/
void die(const char *s){
perror(s);
exit(1);
}

void disableRawMode(){
if(tcsetattr(STDIN_FILENO, TCSAFLUSH, &orig_termios) == -1)
die("tcsetattr");
}

void enableRawMode(){
if(tcgetattr(STDIN_FILENO,&orig_termios) == -1) die("tcgetattr");
atexit(disableRawMode);
struct termios raw = orig_termios;
raw.c_iflag &= ~(BRKINT | INPCK | ISTRIP | IXON | ICRNL);
raw.c_oflag &= ~(OPOST);
raw.c_cflag |= (CS8);
raw.c_lflag &= ~(ECHO | ICANON | IEXTEN | ISIG);
raw.c_cc[VMIN] = 0;
raw.c_cc[VTIME] = 1;

if(tcsetattr(STDIN_FILENO, TCSAFLUSH, &raw) == -1) die("tcsetattr");
}

/*** init ***/
int main(){
enableRawMode();
while(1){
char c = '\0';
if(read(STDIN_FILENO, &c, 1) == -1 && errno != EAGAIN) die("read");
if(iscntrl(c)){
printf("%d\r\n",c);
} else {
printf("%d ('%c')\r\n",c,c);
}
if(c == 'q') break;
}
return 0;
}