表面上看似乎是一个非常简单的问题,但实际上相当复杂。问题的根源在于终端以两种不同的模式运行:原始模式和行模式。行模式是默认模式,表示终端不读取字符,而是读取整行。因此,除非输入整行(或接收到文件结束符),否则您的程序根本不会收到任何输入。终端识别行尾的方法是接收换行符(0x0A),这可以通过按下 Enter 键来引起。更令人困惑的是,在 Windows 机器上,按 Enter 键会生成两个字符(0x0D 和 0x0A)。
因此,您的基本问题是希望有一个单字符界面,但您的终端正处于面向行的(行模式)模式下。
正确的解决方案是将终端切换为原始模式,这样您的程序可以在用户输入时接收字符。此外,建议您在此使用 getchar() 而不是 getc()。区别在于 getc() 将文件描述符作为参数,因此它可以从任何流中读取。而 getchar() 函数仅从标准输入读取,这正是您想要的。因此,它是更具体的选择。在程序完成后,应将终端恢复到原来的状态,因此需要在修改之前保存当前终端状态。
此外,您应该处理终端接收到 EOF(0x04)的情况,用户可以通过按下 CTRL-D 来完成。
以下是执行这些操作的完整程序:
#include <stdio.h>
#include <termios.h>
main(){
tty_mode(0);
set_terminal_raw();
interact();
tty_mode(1);
return 0;
}
void interact(){
while(1){
printf( "\nPlease enter a choice: \n1)quit\n2)Something\n" );
switch( getchar() ){
case 'q': return;
case '2': {
printf( "Hi\n" );
break;
}
case EOF: return;
}
}
}
set_terminal_raw(){
struct termios ttystate;
tcgetattr( 0, &ttystate);
ttystate.c_lflag &= ~ICANON;
ttystate.c_lflag &= ~ECHO;
ttystate.c_cc[VMIN] = 1;
tcsetattr( 0 , TCSANOW, &ttystate);
}
tty_mode( int operation ){
static struct termios original_mode;
if ( operation == 0 )
tcgetattr( 0, &original_mode );
else
return tcsetattr( 0, TCSANOW, &original_mode );
}
正如您所看到的,这个似乎相当简单的问题实际上是非常棘手的。
我强烈推荐一本书来帮助您了解这些问题,它是Bruce Molay的《理解Unix/Linux编程》。第6章详细解释了以上所有内容。
if (*choice == '2') printf("Hi\n"); else printf("Oops: %x\n", *choice & 0xff );
- wildplasserfgets
和一个更大的缓冲区,比如80个字符左右。或者在那里添加另一个循环,读取直到下一个换行符。 - Fred Larsonwhile(choice != 'q')
,而不是while(1)
呢?然后把第一个if语句完全删除。 - urnotsamfflush
***),在脏缓冲区上无法可靠地使用getc
。 - Elias Van Ootegem2<enter>
时,数字 2 和一个换行符都会被排队读取到stdin
中。因此,你的getc
循环会先消耗掉数字 2,然后再运行一次以消耗掉换行符。这就产生了额外的提示,但由于你没有为\n
编写 switch case,所以它只是这样做了。然后它最终再次打印提示并等待下一个输入。需要记住的主要思想是,getc
要求你自己管理输入缓冲区中的换行符,这可能很痛苦。使用fgets
方法通常更简单。 - Gene