曲径通幽论坛

 找回密码
 立即注册
搜索
查看: 5877|回复: 3
打印 上一主题 下一主题

对终端进行读写(reading from and writing to the terminal)

[复制链接]

4918

主题

5880

帖子

3万

积分

GROAD

曲径通幽,安觅芳踪。

Rank: 6Rank: 6

积分
34395
跳转到指定楼层
楼主
发表于 2009-3-2 01:00:45 | 只看该作者 回帖奖励 |倒序浏览 |阅读模式
一个有点问题的代码
01 #include <stdio.h>
02 #include <stdlib.h>
03
04 char *menu[] = {
05         "a - add new record",
06         "d - delete record",
07         "q - quit",
08         NULL,
09 };
10
11 int getchoice(char *greet, char *choices[]);
12
13 int main()
14 {
15         int choice = 0;
16         do
17         {
18                 choice = getchoice("Please select an cation", menu);
19                 printf("You have chosen: %c\n", choice);
20         }while(choice != 'q');
21
22         exit(0);
23 }
24
25 int getchoice(char *greet, char *choices[])
26 {
27         int chosen = 0;
28         int selected;
29         char **option;
30
31         do {
32                 printf("Choice: %s\n", greet);
33                 option = choices;
34                 while(*option) {         /*输出menu选项*/
35                         printf("%s\n",*option);
36                         option++;
37                 }
38
39         selected = getchar();   /*获取用户输入*/
40         option = choices;
41
42         while(*option) {         /*检查是否为a,d 或者 q*/
43                 if(selected == *option[0]) {
44                         chosen = 1;
45                         break;
46                 }
47                 option++;
48         }
49
50         if(!chosen) {
51                 printf("Incorrect choic, select again\n");
52         }
53
54         } while(!chosen);
55
56         return selected;
57 }
运行及输出
[root@localhost C]# ./menu-1.exe
Choice: Please select an cation
a - add new record
d - delete record
q - quit
a
You have chosen: a
Choice: Please select an cation
a - add new record
d - delete record
q - quit
Incorrect choic, select again
Choice: Please select an cation
a - add new record
d - delete record
q - quit
q
You have chosen: q

由上程序,发现即使正确的输入 a 选项,那么程序仍然会输出:Incorrect choic, select again 。为什么呢?

4918

主题

5880

帖子

3万

积分

GROAD

曲径通幽,安觅芳踪。

Rank: 6Rank: 6

积分
34395
沙发
 楼主| 发表于 2009-3-3 13:09:10 | 只看该作者

对重定向输出进行处理(handling redirected output)

经常可以看到,Linux 程序,甚至是交互式的 Linux 程序,会把它们输入输出重定向到一个文件或者其它文件中。

那么把上面的程序重定向到一个文件中看一下:
[root@localhost C]# ./menu-1.exe > menu-1-file
aq
再看一下 menu-1-file 文件:
[root@localhost C]# cat menu-1-file
Choice: Please select an cation
a - add new record
d - delete record
q - quit
You have chosen: a
Choice: Please select an cation
a - add new record
d - delete record
q - quit
You have chosen: q

这可以看作是成功的,因为输出确实是重定向到文件中而不是终端上。然后,你有时可能不想这么做,或者你想把你希望用户看到的提示和其他的可以安全重定向的输出分别对待。

通过查找出底层文件描述符是否关联着一个终端,你可以知道标准输出是否已经被重定向。isatty 系统调用可以完成这个功能。你只要简单的传递给它一个合法的文件描述符,它就会测试并查看这个文件描述符是否连接到了一个终端上。函数定义如下:
#include <unistd.h>
int isatty(int fd);

如果打开的文件描述符 fd 已经连接到了终端则 isatty 系统调用返回 1,否则返回 0。

如果 stdout 已经被重定向了该怎么办?只是简单的退出那还不够,因为用户不知道程序为什么运行失败。把信息打印到 stdout 也无济于事,因为它肯定已经重定向到其他地方去。一个解决的办法是写到标准错误流 stderr 中,这不会被 shell 的 “> file”命令重定向。

修改后的代码
01 #include <stdio.h>
02 #include <stdlib.h>
03 #include <unistd.h>
04
05 char *menu[] = {
06         "a - add new record",
07         "d - delete record",
08         "q - quit",
09         NULL,
10 };
11
12 int getchoice(char *greet, char *choices[]);
13
14 int main()
15 {
16         int choice = 0;
17
18         if(!isatty(fileno(stdout))) {
19                 fprintf(stderr,"you are not a terminal!\\n");
20                 exit(1);
21         }
22
23         do
24         {
25                 choice = getchoice("Please select an cation", menu);
26                 printf("You have chosen: %c\\n", choice);
27         }while(choice != 'q');
28
29         exit(0);
30 }
31
32 int getchoice(char *greet, char *choices[])
33 {
34         int chosen = 0;
35         int selected;
36         char **option;
37
38         do {
39                 printf("Choice: %s\\n", greet);
40                 option = choices;
41                 while(*option) {         /*输出menu选项*/
42                         printf("%s\\n",*option);
43                         option++;
44                 }
45
46         do {
47                 selected = getchar();   /*获取用户输入*/
48         }while(selected == '\\n');
49
50         option = choices;
51
52         while(*option) {         /*检查是否为a,d 或者 q*/
53                 if(selected == *option[0]) {
54                         chosen = 1;
55                         break;
56                 }
57                 option++;
58         }
59
60         if(!chosen) {
61                 printf("Incorrect choic, select again\\n");
62         }
63
64         } while(!chosen);
65
66         return selected;
67 }
运行及输出1
[root@localhost C]# ./menu-1.exe
Choice: Please select an cation
a - add new record
d - delete record
q - quit
q
You have chosen: q
运行及输出2
[root@localhost C]# ./menu-1.exe > file
you are not a terminal!

上面的代码,使用 isatty() 函数测试标准输出是否和终端连接,如果没有连接则程序停止执行。这也是shell用来决定是否提供提示符的检测手段。同时重定向 stdout 和 stderr 这样的做法是可能并且常见的。比如如下把一个错误流定向到一个不同的文件中:
[root@localhost C]# ./menu-1.exe >file 2>file.error
[root@localhost C]# cat file.error
you are not a terminal!

或者如下把两个输出流一起定向到一个单独的文件中:
[root@localhost C]# ./menu-1.exe >file 2>&1
[root@localhost C]# cat file
you are not a terminal!

4918

主题

5880

帖子

3万

积分

GROAD

曲径通幽,安觅芳踪。

Rank: 6Rank: 6

积分
34395
板凳
 楼主| 发表于 2009-3-3 02:41:58 | 只看该作者

cononical versus Non-Canonical Modes

在系统配置缺省情况下,终端的输入知道用户按下回车键后才会传递给程序。在大多数情况里这是有好处的,因为它允许用户用退格键或删除键来修正自己的打字错误。只有当用户满意他们的输入时,他们才会按下回车键,然后把输入传递给程序。

这种行为被成为“授权模式”(cannonical mode) 或 “标准模式”(standard mode)。所有的输入是以行为单位进行处理的(in term of 以...方式)。在输入行完成之前(一般是用户按下回车之前),对于包括退格在内的所有击键处理是由终端接口管理的,应用程序读不到任何字符。

相反的一面,就是 “非授权模式”(non-cononical mode),这里应用程序就有了对输入字符的更多控制权。

linux 终端处理机许多有用的功能之一是,通过把中断字符翻译成信号(例如当你按下 Ctrl+C 来终止你的程序)并且可以自动执行退格与删除进程,这样你不必每一次在你写的程序里重新去实现它们。

那么,上面的那个程序到底是怎么一回事呢?是这样的: linux会在输入回车之前把输入的字符保存起来,然后把用户选中的字符和紧随其后的回车字符一起送到程序里去。这样,用户对菜单每做一次选择,程序就会调用 getchar() 函数对选择字符进行处理,然后再调用一次 getchar(),而这次它将会立即返回一个回车符。

程序实际上看到的字符并不是一个 ASCII 的回车字符 CR(carriage return,十进制13,十六进制0D),而是一个行进纸字符LR(a line feed,十进制10,十六进制0A)。这是因为,就其内部原理来说(internally),Linux(像UNIX)总是用一个行进纸字符来结束文本行。也就是说(that is),UNIX使用一个单独的行进纸字符表示一个换行(newline);而其它操作系统,比如 MS-DOS 要用两个字符来表示换行,一个回车加一个行进纸字符。如果一个输入或输出设备发送或请求一个回车,linux 终端会负责弄好它。这里,如果你习惯了MS-DOS或其它环境,这看起来有点奇怪。在 linux 中,一个相当不错的好处是,对于文本文件和二进制文件并没有什么不同。仅当你向终端,打印机或扫描仪写入或从中输出时,才会对回车进行处理。

修改程序过滤掉多余的行进纸字符(line feed):
do {
                selected = getchar();   /*获取用户输入*/
        }while(selected == '\\n');
因为 getchar() 是读取键盘缓冲区中的数据,所以如果一次输入多个字符,那么整个程序都会依次的读取这些字符,除了回车符外。如下输出:
Choice: Please select an cation
a - add new record
d - delete record
q - quit
akq
You have chosen: a
Choice: Please select an cation
a - add new record
d - delete record
q - quit
Incorrect choic, select again
Choice: Please select an cation
a - add new record
d - delete record
q - quit
You have chosen: q

4918

主题

5880

帖子

3万

积分

GROAD

曲径通幽,安觅芳踪。

Rank: 6Rank: 6

积分
34395
地板
 楼主| 发表于 2009-3-4 11:07:04 | 只看该作者

与终端对话(Talking to the terminal)

如果你需要阻止你程序中与用户交互的那部分不被重定向,但仍允许其作用在其他的输入或输出上,你需要把交互部分与 stdout 和 stderr 分开。你可以通过直接读写终端来做到这一点。因为 linux 天生就是一个多任务系统,经常有许多终端对其直连或通过网络连接 ,那我们如何才能发现有正确的终端可用呢?

幸运的是, linux 和 UNIX 通过提供一个特殊的设备 -- /dev/tty(这总是当前终端或登录会话),使这件事情变得容易。因为 linux 对所有的东西都认为是一个文件,所以你可以使用操作普通文件的方法来读写 /dev/tty。

测试程序
01 #include <stdio.h>
02 #include <unistd.h>
03 #include <stdlib.h>
04
05 char *menu[] = {
06         "a - add new record",
07         "d - delete record",
08         "q - quit",
09         NULL,
10         };
11
12 int getchoice(char *greet, char *choices[], FILE *in, FILE *out);
13
14 int main()
15 {
16         int choice = 0;
17         FILE *input;
18         FILE *output;
19
20         if(!isatty(fileno(stdout))) {
21                 fprintf(stderr, "you are not a terminal, OK.\\n");
22         }
23
24         input = fopen("/dev/tty", "r");
25         output = fopen("/dev/tty", "w");
26         if(!input || !output) {
27                 fprintf(stderr, "Unable to open /dev/tty\\n");
28                 exit(1);
29         }
30
31         do {
32                 choice = getchoice("Please select an action", menu, input, output);
33                 printf("You have chosen: %c\\n", choice);
34         } while(choice != 'q');
35         exit(0);
36 }
37
38 int getchoice(char *greet, char *choices[], FILE *in, FILE *out)
39 {
40         int chosen = 0;
41         int selected;
42         char **option;
43
44         do {
45                 fprintf(out, "Choice: %s\\n", greet);
46                 option = choices;
47
48                 while(*option) {
49                         fprintf(out, "%s\\n", *option);
50                         option++;
51                 }
52
53          do {
54                 selected = fgetc(in);
55         } while(selected == '\\n');
56
57         option = choices;
58
59         while(*option) {
60                 if(selected == *option[0]) {
61                         chosen = 1;
62                         break;
63                 }
64                 option++;
65         }
66         if(!chosen) {
67                 fprintf(out, "Incorrect choice, select again\\n");
68         }
69
70         }while(!chosen);
71
72         return selected;
73 }
运行输出
[root@localhost C]# ./menu-2.exe > file
you are not a terminal, OK.
Choice: Please select an action
a - add new record
d - delete record
q - quit
almq    #输入内容
Choice: Please select an action
a - add new record
d - delete record
q - quit
Incorrect choice, select again
Choice: Please select an action
a - add new record
d - delete record
q - quit
Incorrect choice, select again
Choice: Please select an action
a - add new record
d - delete record
q - quit
[root@localhost C]# cat file
You have chosen: a
You have chosen: q

在“运行输出”中可以看到,错误的选项输入返回的提示会直接显示出来;而正确的选项输入则被重定向到了文件 file 中。
这是因为,如果是错误的选项输入,那么经由 fprintf(out, "Incorrect choice, select again\\n"); 这个语句可以知道,输出被强制从当前的tty中输出;而正确的输入选项,则会经由 ./menu-2.exe > file 重定向到文件 file 中。

注意,在
20         if(!isatty(fileno(stdout))) {
21                 fprintf(stderr, "you are not a terminal, OK.\\n");
22         }
中,这用来判断当前的标准输出是否连接一个终端上。可以做这么一个实验:

如果是在 linux 的桌面环境下开启的一个终端窗口,那么就会打印出:
you are not a terminal, OK
字样,表明当前的标准输出窗口不是一个终端。

如果在 windows 中用如 putty 这样的一个连接工具进行 ssh 连接到 linux 中,则这个提示不会出现,因为那样的连接方式所对的客户机就认为是一个终端。
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

小黑屋|手机版|Archiver|曲径通幽 ( 琼ICP备11001422号-1|公安备案:46900502000207 )

GMT+8, 2025-5-4 00:07 , Processed in 0.123740 second(s), 21 queries .

Powered by Discuz! X3.2

© 2001-2013 Comsenz Inc.

快速回复 返回顶部 返回列表