【问题标题】:Why can this code read from stdin when it should be empty?为什么该代码应该为空时可以从标准输入读取?
【发布时间】:2016-11-18 22:01:24
【问题描述】:

代码来自 Kilo 项目:

/*
 * Use write() and terminal escape (ESC) sequence
 * to query cursor position.
 */

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

#define ESC 27

int main(int argc, char const *argv[]) {
    char buf[32];
    unsigned int i = 0;
    int rows, cols;

    write(STDOUT_FILENO, "\033[6n", 4);

    while (i < sizeof(buf)-1) {
        if (read(STDIN_FILENO,buf+i,1) != 1) break;
        if (buf[i] == 'R') break;
        i++;
    }
    buf[i] = '\0';

    /* Parse it. */
    if (buf[0] != ESC || buf[1] != '[') return -1;
    if (sscanf(buf+2,"%d;%d",&rows,&cols) != 2) return -1;
    printf("%d %d\n", rows, cols);

    return 0;
}

OS X terminal.app 中的结果是:

➜  /Users/name/Desktop/demo ./write
^[[44;1R
44 1
➜  /Users/name/Desktop/demo 
➜  /Users/name/Desktop/demo 

结果有 5 行,但我无法理解动作:

  1. 为什么它会在第 2 行输出这些字符?因为write() 函数向STDOUT 写入了一些字符?如果是,我该如何抑制这个输出?

  2. 为什么read() 函数可以从STDIN 中读取内容?我只是觉得STDIN现在应该是空的,因为我没有按任何键。

  3. 在第 2 行之后,它将阻塞。为什么我必须按 ENTER

  4. 为什么第 4 行是空的?是因为我刚刚按下了ENTER

在 OS X 10.11.6 Terminal.app 中运行

【问题讨论】:

  • ^[[ 是一个终端控制序列。例如“将光标移动到位置 x,y”,或“将文本颜色更改为红色”。
  • 是的,我知道控制序列的功能。但为什么它可以从 STDIN 读取?
  • read 函数将阻塞,直到有内容可供阅读。按下回车键后,该行将发送到您的程序以便它可以读取。
  • @user3121023 所以你的意思是转义码的结果将显示在STDIN然后read()可以从STDIN读取光标位置?如果是,如何抑制第 2 行输出?

标签: c macos io terminal


【解决方案1】:

转义序列\e[6n 用于向终端查询光标位置。终端通过输入字符来回答,就好像它们是由用户输入的一样。答案格式为\e[y;xR,其中y 是行号,x 是列号,均基于1。这里\e[44;1R 由终端以线路模式回显,就像任何其他用户输入一样。请注意,转义字节回显为^[。您可以使用退格键编辑此输出。尝试修改列号,将其更改为^[[44;11R,然后按回车查看效果。

程序尝试读取此输入,但由于终端处于行模式,您需要键入 ENTER 键才能使系统可以使用整行输入。

请注意,程序会通过单独的read() 系统调用逐字节地从系统中读取此输入。它在收到R 时停止读取,因此换行符保留在系统缓冲区中。

程序的sscanf()解析\e[44;1,打印44 1(第44行,第1列)并退出。

shell 回显命令提示符,读取挂起的换行符并回显另一个命令提示符。

我不确定如何抑制 line2 输出。我怀疑重定向标准输出会有所作为。

write() 之前使用stty() 系统调用将终端设置为原始模式应该会阻止此输出以及对ENTER 键的需要。如果您更改终端模式,也就是 line 规则,请记住保存当前设置并在退出程序之前恢复它们。

【讨论】:

  • 感谢您的回答。那么如何抑制line2输出呢?重定向 STDOUT?
  • @merito:我用一个潜在的解决方案更新了答案。
【解决方案2】:

如果你不想返回,你需要让终端退出规范模式。如果您希望它不回显响应字符串,则需要禁用回显。你用tcsetattr 做这两个。添加到您的程序中:

在顶部:

#include <errno.h>
#include <termios.h>

main开头:

struct termios old, modified;

if (tcgetattr(STDIN_FILENO, &old) < 0) {
    if (errno == ENOTTY)
        fprintf(stderr, "input is not a terminal -- can't query position\n");
    else
        perror("tcgetattr");
    exit(1); }
modified = old;
modified.c_lflag &= ~(ICANON | ECHO);
tcsetattr(STDIN_FILENO, TCSANOW, &modified);

main末尾(返回前):

tcsetattr(STDIN_FILENO, TCSANOW, &old);

这会在程序启动时禁用规范模式和回显,然后恢复以前的设置。

另外,您实际上希望将查询序列写入STDIN_FILENO 而不是STDOUT_FILENO。这样,即使您将输出重定向到某个地方,它也会继续正常工作......

【讨论】:

    猜你喜欢
    • 2016-11-03
    • 1970-01-01
    • 2010-09-09
    • 1970-01-01
    • 1970-01-01
    • 2020-08-21
    • 2020-03-14
    • 1970-01-01
    • 2016-04-19
    相关资源
    最近更新 更多