理解 C 中的读 + 写

Understanding read + write in c

char buf[1];
if (argc == 1) {
    while (read(STDIN_FILENO, buf, 1) > 0) {
        write(1, buf, sizeof(buf));
    }
}

关于这段代码,我有几件事要澄清。我们 运行 它,./exec_file 假设我们只是按回车键。我们移至下一行并读取 1 个字节 '\n',然后将其写入标准输出,使我们再往下一行……很简单。现在假设我们输入 h,然后输入。程序在下一行吐出 h 和一个不可见的 '\n'.

在我们输入 h 后查看代码,它会将其读入缓冲区然后将其写入标准输出,但不知何故程序会等待在下一行将其吐出,直到我按下 Enter..如何?

最后,当我们第一次点击 while 循环时,最初不会读取 return 0 因为我们最初没有输入任何内容??

stdin 与大多数其他流的行为有点不同。

首先,输入是行缓冲的。这意味着在您按下 enter 之前输入不可用。这解释了 h 在您按 enter 之前不会出现。

因为它是一个流,所以它实际上没有尽头。调用不会在没有数据可读时失败,而是会阻塞直到某些数据可用(或直到程序接收到信号)。套接字的工作方式相同。

阻塞行为can be turned off 使用fcntl :

int fd = STDIN_FILENO;
int flags = fcntl(fd, F_GETFL, 0);
fcntl(fd, F_SETFL, flags | O_NONBLOCK);

如果您仍然希望发生阻塞,但想逐个字符地读取,您可以使用 termios 配置如何将输入提供给您的程序。请参阅下面的代码。

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

int main()
{
    char buf[1];
    struct termios term, term_orig;

    if (tcgetattr(0, &term_orig)) {
        printf("tcgetattr failed\n");
        exit(-1);
    }

    term = term_orig;

    term.c_lflag &= ~ICANON;
    term.c_lflag |= ECHO;
    term.c_cc[VMIN] = 1;
    term.c_cc[VTIME] = 0;

    if (tcsetattr(0, TCSANOW, &term)) {
        printf("tcsetattr failed\n");
        exit(-1);
    }

    while (read(0, buf, 1) > 0) {
        write(1, buf, sizeof(buf));
    }
    return 0;
}

终端默认行缓冲,因为它处于规范模式。来自 Linux 手册 tcgetattr(3):

Canonical and noncanonical mode

The setting of the ICANON canon flag in c_lflag determines whether the terminal is operating in canonical mode (ICANON set) or noncanonical mode (ICANON unset). By default, ICANON set.

In canonical mode:

  • Input is made available line by line. An input line is available when one of the line delimiters is typed (NL, EOL, EOL2; or EOF at the start of line). Except in the case of EOF, the line delimiter is included in the buffer returned by read(2).

  • Line editing is enabled (ERASE, KILL; and if the IEXTEN flag is set: WERASE, REPRINT, LNEXT). A read(2) returns at most one line of input; if the read(2) requested fewer bytes than are available in the current line of input, then only as many bytes as requested are read, and the remaining characters will be available for a future read(2).

您可以通过使用适当的标志调用 tcgetattr 来关闭终端上的规范模式。首先禁用规范模式;然后将超时设置为0;将最小读取设置为 1 用于阻塞读取或 0 用于非阻塞读取。通常习惯上也禁用本地回显,否则您键入的所有内容仍将自动可见(并在您的程序中显示两次):

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

int main() {
    struct termios old_settings, new_settings;
    int is_terminal;

    // check whether the stdin is a terminal to begin with
    if (is_terminal = isatty(STDIN_FILENO)) {     
        // get the old settings
        tcgetattr(STDIN_FILENO, &old_settings);

        new_settings = old_settings;

        // disable canonical mode and echo
        new_settings.c_lflag &= (~ICANON & ~ECHO);

        // at least one character must be written before read returns
        new_settings.c_cc[VMIN] = 1;

        // no timeout
        new_settings.c_cc[VTIME] = 0;

        tcsetattr(STDIN_FILENO, TCSANOW, &new_settings);
    }

    while (read(STDIN_FILENO, buf, 1) > 0) {
        // add this here so that you can verify that it is character by character,
        // and not the local echo from the terminal
        write(STDOUT_FILENO, ">", 1);
        write(STDOUT_FILENO, buf, sizeof(buf));
    }

    // finally restore the old settings if it was a terminal
    if (is_terminal) {
        tcsetattr(STDIN_FILENO, TCSANOW, &old_settings);
    }
    return 0;
}