【问题标题】:How to make arrow keys and backspace work correctly when asking input from user in C program using termios.h?使用 termios.h 在 C 程序中询问用户输入时,如何使箭头键和退格键正常工作?
【发布时间】:2014-12-24 06:05:37
【问题描述】:

所以我有以下代码,它基本上只是读取用户输入的字符并打印它们,直到输入“q”。

#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<termios.h>

int main(void) {
    char c; 
    static struct termios oldtio, newtio;
    tcgetattr(0, &oldtio);
    newtio = oldtio;
    newtio.c_lflag &= ~ICANON;
    newtio.c_lflag &= ~ECHO;
    tcsetattr(0, TCSANOW, &newtio);

    printf("Give text: ");
    fflush(stdout);
    while (1) {
        read(0, &c, 1);
        printf("%c", c);
        fflush(stdout);
        if (c == 'q') { break; }
    }
    printf("\n"); 
    tcsetattr(0, TCSANOW, &oldtio);

    return 0;
}

在 main 函数的开头,我关闭了规范模式,以便用户在输入时能够看到他的输入。我还关闭了回声,例如,当按下向上箭头键时,不会弹出诸如“^[[A”之类的东西。这可行,但我也可以将光标移动到终端窗口的上排,这并不好。有没有办法解决这个问题,让用户只能在当前行内移动?

另一个问题是退格。当我按下它时,程序会打印一个奇怪的符号(我假设它是 0x7f),而不是擦除光标当前位置左侧的字符。我应该以某种方式适当地处理程序中的退格键输出,但我不知道该怎么做,因为它是这个奇怪的十六进制数字。有什么建议吗?

我也一直在考虑使这项工作的一个选项是使用规范模式,以便自动使用箭头键和退格功能。但是,规范模式逐行工作,因此在用户点击“Enter”之前不会出现文本。到目前为止,我还没有想出任何方法让用户在打字时看到他的输入。这甚至可能吗?

请不要使用 ncurses 或 readline 建议。我想使用 termios.h 来做到这一点。

【问题讨论】:

  • 您关闭回显,然后自己回显所有内容。这不是一个成功的策略。回显普通可打印字符,处理和解释控制序列。您需要知道您的控制序列是什么以及它们的含义。为此,您需要一个等效的 terminfo 数据库。由于 terminfo 与 ncurses 密切相关,而您不需要 ncurses,我只能祝您好运。
  • 所以你是说如果没有 ncurses 这甚至不可能?
  • 如果您从头开始重新实现所有内容的一部分并且不介意放弃其他部分的功能,那么没有一切一切皆有可能。您可以将自己限制在一个流行的终端系列中,例如 VT100 和后代,编译您自己的控制序列列表并使用它。否则,您几乎必须至少使用 terminfo(技术上是 (n)curses 的一部分)。
  • @n.m.如何实现另一个依赖 UTF8 编码的 ncurses? UTF8 现在是标准字符集,每个终端仿真器都支持它。这会使新库(以取代 ncurses)更轻吗?我们也不需要 terminfo,对吧?

标签: c linux terminal termios


【解决方案1】:

您查看过手册页吗? (应该是man termios 或查看某处online

在那里我找到了ECHOE 标志,据说它具有以下效果:

如果还设置了 ICANON,则 ERASE 字符会擦除前面的输入字符,而 WERASE 会擦除前面的单词。

这应该可以解决您的退格问题?

我还建议您查看手册页中的示例。例如。您可以执行以下操作:

newtio.c_lflag &= ~(ECHO | ECHOE | ICANON);

...在一行中一次设置多个标志。我知道手册页对于初学者来说很难阅读,但是您会习惯它们,并且使用它们的次数越多,它们查找 C/POSIX 函数等的效率就越高(以防万一,您不使用它们)无论如何)。

箭头键问题:也许你可以试试cfmakeraw()-function;它的描述听起来很有希望。我没有时间进一步调查箭头键。但是,也许您会在手册页中找到其他有用的内容。

顺便说一句:termios 看起来很有趣,我一直想知道某些命令行程序正在使用哪些功能;从你的问题中学到了一些东西,谢谢!

编辑

这个周末我做了更多的研究。按下退格键时打印的“奇怪”符号很容易隐藏。它是 ASCII 值 0x7f。所以添加一个简单的

if (c == 0x7f) { continue; }

...忽略退格键。或者以删除最后一个字符的方式处理它(参见下面的代码示例)。

这个简单的解决方法不适用于箭头键,因为它们不是 ASCII 字符:/ 但是,这两个主题也帮助我处理了这个问题:topic 1topic 2。基本上按下箭头键会导致一系列字符被发送到stdin(有关更多信息,请参见第二个链接)。

这是我的完整代码(我认为)可以按照您希望的方式工作:

#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>
#include <termios.h>

int main(void) {
    char c;
    static struct termios oldtio, newtio;
    tcgetattr(0, &oldtio);
    newtio = oldtio;
    newtio.c_lflag &= ~ICANON;
    newtio.c_lflag &= ~ECHO;
    tcsetattr(0, TCSANOW, &newtio);

    printf("Give text:\n");
    fflush(stdout);
    while (1) {
        c = getchar();

        // is this an escape sequence?
        if (c == 27) {
            // "throw away" next two characters which specify escape sequence
            c = getchar();
            c = getchar();
            continue;
        }

        // if backspace
        if (c == 0x7f) {
            // go one char left
            printf("\b");
            // overwrite the char with whitespace
            printf(" ");
            // go back to "now removed char position"
            printf("\b");
            continue;
        }

        if (c == 'q') {
            break;
        }
        printf("%c", c);
    }
    printf("\n");
    tcsetattr(0, TCSANOW, &oldtio);

    return 0;
}

顺便说一句,您可以通过以下代码获得完整的转义序列:

int main(void) {
    char c;
    while (1) {
        c = getchar();
        printf("%d", c);
    }
    return 0;
}

我想我不必说这个完整的东西是一个相当肮脏的黑客,很容易忘记处理一些特殊的键。例如。在我的代码中,我不处理 page-up/downhome 键... --> 上面的代码还远未完成,但为您提供了一个起点。您还应该看看terminfo,它可以为您提供很多必要的信息;它还应该有助于提供更便携的解决方案。如您所见,这个“简单”的事情可能会变得非常复杂。因此您可能会重新考虑反对 ncurses 的决定:)

【讨论】:

  • 不,它仍然给我“0x7f”符号而不是擦除一个字符,因为正如它所说,必须设置 ICANON,这意味着它应该被打开。如果我打开它,用户在点击“Enter”之前看不到他正在输入的内容。但是感谢您的努力。
  • @JZ555: 嗯,好吧,你是对的......今晚会在家里测试它(我有一个 Linux(->POSIX) 系统可用),也许我能想出别的办法那里……抱歉,我现在帮不上忙。
  • @JZ555 比我想象的要难一些,但我想我现在为你找到了解决方案。请参阅我的答案的 EDIT 部分:)
  • 这正是我开始所需要的。谢谢! :)
【解决方案2】:

实际上,为了处理箭头键,您必须实现一大块 ncurses。有利有弊:在命令行应用程序中使用 ncurses 的主要缺点可能是它通常会清除屏幕。然而,(n)curses 提供了一个函数filter。在 ncurses 源代码中有一个示例程序“test/filter.c”,它通过使用左箭头键作为擦除字符来说明这一点,并将结果行传递给 system() 以运行简单的命令。该示例不到 100 行代码——看起来比上面的示例更简单、更完整。

【讨论】:

    猜你喜欢
    • 2021-11-19
    • 2022-10-09
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2020-09-01
    • 2015-11-10
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多