5

所以我有以下代码,它基本上只是读取用户输入的字符并打印它们,直到输入'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;
}

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

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

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

拜托,没有 ncurses 或 readline 建议。我想使用 termios.h 来做到这一点。

4

2 回答 2

5

你看过手册页吗?(应该是或在网上man termios某处寻找)

在那里我发现了ECHOE据说具有以下效果的标志:

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

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

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

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

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

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

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

编辑

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

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

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

这个简单的解决方法不适用于箭头键,因为它们不是 ASCII 字符:/ 但是,这两个主题也帮助我处理了这个问题:主题 1主题 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/downorhome键... --> 上面的代码还远未完成,但为您提供了一个起点。您还应该看看terminfo哪些可以为您提供很多必要的信息;它还应该有助于提供更便携的解决方案。如您所见,这个“简单”的事情可能会变得非常复杂。所以您可能会重新考虑您对 ncurses 的决定:)

于 2014-10-28T18:07:19.917 回答
0

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

于 2015-02-05T01:05:06.717 回答