Понимание чтения + записи на c

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

У меня есть несколько вещей, которые я хотел бы прояснить по поводу этого фрагмента. Запускаем его, ./exec_file Допустим, мы просто нажимаем Enter. Мы переходим к следующей строке и читаем 1 байт '\n', затем записываем его в stdout, выводя нас вниз еще на одну строку ... достаточно просто. Теперь допустим, что мы набираем h, затем Enter. Программа выдает h в следующей строке с невидимым '\n'.

Глядя на код после того, как мы набираем h, он считывает его в буфер, а затем записывает в стандартный вывод, но каким-то образом программа ждет, чтобы выплюнуть его в следующей строке, пока я не нажму Enter .. как?

Наконец, когда мы впервые попадаем в цикл while, сначала не читаем return 0, поскольку изначально мы ничего не ввели ??


person ajanakos    schedule 01.04.2016    source источник
comment
Стандартный ввод - это строчная буферизация; ввод не передается в вашу программу, пока пользователь не нажмет клавишу ввода.   -  person molbdnilo    schedule 01.04.2016
comment
Ах, так что если у нас есть hhh, тогда введите, он формирует файл, содержащий hhh\n, затем читает   -  person ajanakos    schedule 01.04.2016


Ответы (3)


stdin ведет себя немного иначе, чем большинство других потоков.

Сначала вводится строковая буферизация. Это означает, что ввод недоступен, пока вы не нажмете клавишу ввода. это объясняет, пока h не появится, пока вы не нажмете клавишу ввода.

Поскольку это поток, у него действительно нет конца. Вместо сбоя, когда нет данных для чтения, вызов будет заблокирован до тех пор, пока некоторые данные не станут доступными (или пока программа не получит сигнал). Розетка работает точно так же.

Блокирующее поведение можно отключить с помощью fcntl:

int fd = STDIN_FILENO;
int flags = fcntl(fd, F_GETFL, 0);
fcntl(fd, F_SETFL, flags | O_NONBLOCK);
person Klas Lindbäck    schedule 01.04.2016

Терминал по умолчанию буферизируется по строкам, потому что он находится в каноническом режиме. Из руководств по Linux tcgetattr(3):

Канонический и неканонический режим

Установка флага ICANON canon в c_lflag определяет, работает ли терминал в каноническом режиме (установлен ICANON) или неканоническом режиме (ICANON не установлен). По умолчанию установлен ICANON.

В каноническом режиме:

  • Ввод доступен построчно. Строка ввода доступна, когда набран один из разделителей строк (NL, EOL, EOL2; или EOF в начале строки). За исключением случая EOF, разделитель строк включается в буфер, возвращаемый функцией read (2).

  • Разрешено редактирование строки (ERASE, KILL; и если установлен флаг IEXTEN: WERASE, REPRINT, LNEXT). Read (2) возвращает не более одной строки ввода; если read (2) запросил меньше байтов, чем доступно в текущей строке ввода, то будет прочитано только столько байтов, сколько запрошено, а оставшиеся символы будут доступны для будущего чтения (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;
}
person Antti Haapala    schedule 01.04.2016

Если вы по-прежнему хотите, чтобы блокировка происходила, но хотите читать символ за символом, вы можете использовать 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;
}
person rools    schedule 01.04.2016
comment
Используйте isatty(STDIN_FILENO), чтобы узнать, работает ли этот подход, поскольку стандартный ввод не работает. обязательно терминал. - person ; 01.04.2016