有啥方法可以在规范模式下处理转义键?
Posted
技术标签:
【中文标题】有啥方法可以在规范模式下处理转义键?【英文标题】:Any way to process escape key in canonical mode?有什么方法可以在规范模式下处理转义键? 【发布时间】:2014-12-08 15:11:34 【问题描述】:在 unix 纯 C termios 编程中,如果我使用规范模式接收用户的一行输入,我该如何处理转义键?通常,如果用户正在输入一行文本并按下转义键,则不会发生任何事情。如果用户按转义键,我想取消当前输入。我知道我可以处理单个字符,但是我失去了规范模式的所有好处(退格等)。
【问题讨论】:
为什么不使用像readline
这样的东西,它可以让您充分控制输入处理?
将kill char设置为ESC
?但是当你这样做时要小心。您应该在程序退出之前恢复用户的原始偏好。
readline 已经将 Esc 键用于一组精心设计的目的,这些目的与我想要完成的目标完全相切。我这里的 UI 是用于数据输入,而不是命令处理,所以不希望有一个复杂的行编辑器;我只需要基本的规范行为,但要处理转义键。
【参考方案1】:
原创
感谢Jonathan Leffler for his comment that hinted me at the right direction,底部是我带注释的第一个termios
程序演示者(谢谢!)。
关键是在当前终端的文件描述符上使用tcgetattr(ttyfd, &attributes)
将其当前属性检索到struct termios
中,编辑属性,然后使用tcsetattr(ttyfd, when, &attributes)
应用更改。
其中一个属性是“kill”字符 - 导致整个当前缓冲行被丢弃的字符。它是通过索引struct termios
的c_cc
成员数组并将attr.c_cc[VKILL]
设置为任何人想要的(这里是Esc,等于八进制033
)。
kill 字符应该在退出时恢复到之前的值。
#include <termios.h>
#include <fcntl.h>
#include <stdio.h>
int main()
char buf[80];
int numBytes;
struct termios original, tattered;
int ttyfd;
/* Open the controlling terminal. */
ttyfd = open("/dev/tty", O_RDWR);
if(ttyfd < 0)
printf("Could not open tty!\n");
return -1;
/**
* Get current terminal properties, save them (including current KILL char),
* set the new KILL char, and make this the new setting.
*/
tcgetattr(ttyfd, &original);
tattered = original;
tattered.c_cc[VKILL] = 033;/* New killchar, 033 == ESC. */
tcsetattr(ttyfd, TCSANOW, &tattered);
/**
* Simple test to see whether it works.
*/
write(1, "Please enter a line: ", 21);
numBytes = read(0, buf, sizeof buf);
write(1, buf, numBytes);
/**
* Restore original settings.
*/
tcsetattr(ttyfd, TCSANOW, &original);
/* Clean up. */
close(ttyfd);
return 0;
此演示似乎可以在 Mac OS X 10.6.8 上运行。我也在 Linux 上对此进行了测试,显然 Esc 杀死缓冲区似乎是默认设置,就好像我打印出c_cc[VKILL]
我得到27 == 033 == ESC
。
编辑
以下尝试尽可能模仿您在评论中描述的行为。它将c_cc[VEOL2]
设置为Esc; EOL2 是替代的 End-of-Line。它还 删除 Esc 作为杀死字符,因为您想接收该行。
现在发生的情况是,如果按下正常的Ret,一切正常。但是,如果按下 Esc,缓冲区中的最后一个字符将设置为 Esc,这是可以测试的条件(尽管只有在首先读取和缓冲整行之后) .
以下是根据您明确说明的演示器。它等待一行输入并将其回显
<CANCELLED>
如果该行以 Esc 和 终止
<NORMAL >
如果该行以 Ret 终止。
享受吧!
#include <termios.h>
#include <fcntl.h>
#include <stdio.h>
int main()
char buf[80];
int numBytes;
struct termios original, tattered;
int ttyfd;
/* Open the controlling terminal. */
ttyfd = open("/dev/tty", O_RDWR);
if(ttyfd < 0)
printf("Could not open tty!\n");
return -1;
/**
* Get current terminal properties, save them (including current KILL char),
* set the new KILL char, and make this the new setting.
*/
tcgetattr(ttyfd, &original);
tattered = original;
tattered.c_cc[VKILL] = 0; /* <Nada> */
tattered.c_cc[VEOL2] = 033;/* Esc */
tcsetattr(ttyfd, TCSANOW, &tattered);
/**
* Simple test to see whether it works.
*/
fputs("Please enter a line: ", stdout);
fflush(stdout);
numBytes = read(0, buf, sizeof buf);
if(buf[numBytes-1]==033)/* Last character is Esc? */
buf[numBytes-1] = '\n';/* Substitute with newline */
fputs("\n<CANCELLED> ", stdout); /* Print newline to move to next line */
else
fputs("<NORMAL > ", stdout);
fwrite(buf, 1, numBytes, stdout);
/**
* Restore original settings.
*/
tcsetattr(ttyfd, TCSANOW, &original);
/* Clean up. */
close(ttyfd);
return 0;
【讨论】:
请注意,您应该真正保留 kill 字符的原始值的副本,并在程序完成后和退出之前恢复它。你会影响所有进程,包括调用它的 shell——至少在原则上是这样。我想对于已经使用终端设置的现代 shell,如果 shell 重置其设置,它很可能是“好的”——但你至少应该用stty -a
或类似的方式验证这一点。
@JonathanLeffler 这很公平;我添加了代码来恢复原始设置。但是,突出显示被破坏了;不知道为什么,就好像整个网站的L&F突然发生了微妙的变化。
如果您指的是 SO 的 L&F,那么是的,他们今天刚刚将他们不久前对 Meta Stack Overflow 所做的更改(参见 Feedback requested: Stack Overflow design update)传播到主要的 Stack Overflow。大多数情况下它是好的,但它需要一点时间来适应。 (另见Is this flat theme new or is there an option to change it back?)
泰勒在 2014 年 10 月提出了这个问题;他很可能已经克服了这个障碍,并且没有一个简单的方法来决定它是否适合他。
这是一个有用的技术,但这里的目标是取消输入,而不是清除命令行。换句话说,需要将该行返回给程序,就像按下 Enter 一样,除了因为最后一个字符是转义字符,它会提示应用程序知道它是取消。【参考方案2】:
您需要使用tcsetattr()
函数将EOF
字符设置为ESC
而不是Enter
。更多详细信息请访问http://pubs.opengroup.org/onlinepubs/7908799/xbd/termios.html#tag_008_001_009
【讨论】:
【参考方案3】:这是我的getLine()
函数的略微修改版本,用于来自用户的稳健输入。您可以查看原始 here 的详细信息,但此版本已被修改为使用 termios
内容,可以对输入进行一定程度的控制。
因为termios
的工作级别低于标准 C 输入,所以它也会影响到这一点。
首先,getLine()
函数所需的标头和返回值:
#include <termios.h>
#include <fcntl.h>
#include <stdio.h>
#include <string.h>
#define OK 0
#define NO_INPUT 1
#define TOO_LONG 2
#define TERM_PROB 3
接下来,一个帮助函数将终端恢复到其原始状态,这使您可以轻松地从 getLine()
返回一个值,知道终端将保持其原始状态。
static int revertTerm (int fd, struct termios *ptio, int old, int rc)
// Revert the terminal to its original state then return
// specified value.
ptio->c_cc[VKILL] = old;
tcsetattr (fd, TCSANOW, ptio);
close (fd);
return rc;
接下来是实际的getLine()
函数本身,它修改终端属性以使 ESC 成为终止字符,然后调用fgets()
以及所有额外的提示,检测缓冲区溢出,刷新输入到行尾等等。
作为此功能的一部分,在用户位于fgets()
内期间,修改后的终端行为处于活动状态,您可以使用 ESC 清除该行。
static int getLine (char *prmpt, char *buff, size_t sz)
int old, fd, ch, extra;
struct termios tio;
// Modify teminal so ESC is KILL character.
fd = open ("/dev/tty", O_RDWR);
if (fd < 0)
return TERM_PROB;
tcgetattr (fd, &tio);
old = tio.c_cc[VKILL];
tio.c_cc[VKILL] = 0x1b;
tcsetattr (fd, TCSANOW, &tio);
// Get line with buffer overrun protection.
if (prmpt != NULL)
printf ("%s", prmpt);
fflush (stdout);
if (fgets (buff, sz, stdin) == NULL)
return revertTerm (fd, &tio, old, NO_INPUT);
// If it was too long, there'll be no newline. In that case, we flush
// to end of line so that excess doesn't affect the next call.
if (buff[strlen(buff)-1] != '\n')
extra = 0;
while (((ch = getchar()) != '\n') && (ch != EOF))
extra = 1;
return revertTerm (fd, &tio, old, (extra == 1) ? TOO_LONG : OK);
// Otherwise remove newline and give string back to caller.
buff[strlen(buff)-1] = '\0';
return revertTerm (fd, &tio, old, OK);
最后是一个测试程序,以便您检查它的行为。基本上,它允许您输入最多 20 个字符的行,然后将它们打印出来并带有状态(太长、没有输入等)。
如果在输入过程中的任何时候按ESC,它将终止该行并重新开始。
输入exit
会导致程序退出。
// Test program for getLine().
int main (void)
int rc, done = 0;
char buff[21];
while (!done)
rc = getLine ("Enter string (ESC to clear, exit to stop)> ",
buff, sizeof(buff));
if (rc == NO_INPUT)
// Extra NL since my system doesn't output that on EOF.
printf ("\nNo input\n");
else if (rc == TOO_LONG)
printf ("Input too long [%s]\n", buff);
else
done = (strcmp (buff, "exit") == 0);
if (!done)
printf ("OK [%s]\n", buff);
return 0;
【讨论】:
以上是关于有啥方法可以在规范模式下处理转义键?的主要内容,如果未能解决你的问题,请参考以下文章