Чтение всего, что в настоящее время введено в стандартный ввод

Я хочу прочитать все, что находится на стандартном вводе, через 10 секунд, а затем сломаться. Код, который я смог написать до сих пор:

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

int main() {
  sleep(10);
  char c;
  while (1) { // My goal is to modify this while statement to break after it has read everything.
    c = getchar();
    putchar(c);
  }
  printf("Everything has been read from stdin");
}

Поэтому, когда буква «с» вводится до истечения 10 секунд, она должна напечатать «с» (после того, как sleep будет выполнено), а затем «Все было прочитано со стандартного ввода».

До сих пор я пробовал:

  • Проверка, является ли cEOF -> getchar и подобные функции никогда не возвращают EOF для stdin
  • Использование функции типа stat для stdin -> stat-ing stdin всегда возвращает 0 для размера (st_size).

Проверка, является ли c EOF -> getchar и подобные функции никогда не возвращают EOF для stdin Это потому, что getchar() возвращает int, а не char. Втискивание возвращаемого значения в char лишает возможности обнаруживать EOF. Вам нужно заменить char c; на int c;.

Andrew Henle 20.11.2022 13:17

@AndrewHenle Изменение char c; на int c; и while (1) { на while ((c = getchar()) != EOF) { не решило проблему для меня.

gurkensaas 20.11.2022 13:22

@AndrewHenle Чтобы уточнить, теперь я могу сделать echo "hello world" | ./myprogram, а затем он печатает «hello world», а затем «Все было прочитано со стандартного ввода», но чтение из stdin таким образом, а не пользовательский ввод в течение периода sleep не является моей целью.

gurkensaas 20.11.2022 13:28

@user3121023 user3121023 Я знаю, что терминал обычно буферизуется. Мой вопрос в том, если я разбуферю его или нажму Enter, как я узнаю, что больше нечего читать?

gurkensaas 20.11.2022 14:22

@ user3121023 Я бы предпочел подход termios. Не могли бы вы привести пример в ответе?

gurkensaas 20.11.2022 15:06
Шаблоны Angular PrimeNg
Шаблоны Angular PrimeNg
Как привнести проверку типов в наши шаблоны Angular, использующие компоненты библиотеки PrimeNg, и настроить их отображение с помощью встроенной...
Создайте ползком, похожим на звездные войны, с помощью CSS и Javascript
Создайте ползком, похожим на звездные войны, с помощью CSS и Javascript
Если вы веб-разработчик (или хотите им стать), то вы наверняка гик и вам нравятся "Звездные войны". А как бы вы хотели, чтобы фоном для вашего...
Документирование API с помощью Swagger на Springboot
Документирование API с помощью Swagger на Springboot
В предыдущей статье мы уже узнали, как создать Rest API с помощью Springboot и MySql .
Начала с розового дизайна
Начала с розового дизайна
Pink Design - это система дизайна Appwrite с открытым исходным кодом для создания последовательных и многократно используемых пользовательских...
Шлюз в PHP
Шлюз в PHP
API-шлюз (AG) - это сервер, который действует как единая точка входа для набора микросервисов.
14 Задание: Типы данных и структуры данных Python для DevOps
14 Задание: Типы данных и структуры данных Python для DevOps
проверить тип данных используемой переменной, мы можем просто написать: your_variable=100
3
5
139
2
Перейти к ответу Данный вопрос помечен как решенный

Ответы 2

Вы можете использовать функцию select, чтобы увидеть, есть ли что-то для чтения на стандартном вводе с тайм-аутом, который начинается с 10 секунд. Когда он что-то обнаруживает, вы читаете символ и проверяете наличие ошибок или EOF. Если все хорошо, то вы снова вызываете select, сокращая время ожидания на прошедшее до сих пор время.

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/select.h>
#include <sys/time.h>
#include <time.h>

struct timeval tdiff(struct timeval t2, struct timeval t1)
{
    struct timeval result;

    result.tv_sec = t2.tv_sec - t1.tv_sec;
    result.tv_usec = t2.tv_usec - t1.tv_usec;
    while (result.tv_usec < 0) {
        result.tv_usec += 1000000;
        result.tv_sec--;
    }
    return result;
}

int cmptimestamp(struct timeval t1, struct timeval t2)
{
    if (t1.tv_sec > t2.tv_sec) {
        return 1;
    } else if (t1.tv_sec < t2.tv_sec) {
        return -1;
    } else if (t1.tv_usec > t2.tv_usec) {
        return 1;
    } else if (t1.tv_usec < t2.tv_usec) {
        return -1;
    } else {
        return 0;
    }
}

int main()
{
    struct timeval cur, end, delay;
    int rval, len = 0;
    fd_set fds;

    gettimeofday(&cur, NULL);
    end = cur;
    end.tv_sec += 10;
    FD_ZERO(&fds);
    FD_SET(0, &fds);

    if (fcntl(0, F_SETFL, O_NONBLOCK) == -1) {
        perror("fcntl failed"); 
        exit(1);
    }
    do {
        delay = tdiff(end, cur);
        rval = select(1, &fds, NULL, NULL, &delay);
        if (rval == -1) {
            perror("select failed");
        } else if (rval) {
            char c;
            len = read(0, &c, 1);
            if (len == -1) {
                perror("read failed");
            } else if (len > 0) {
                printf("c=%c (%d)\n", c, c);
            } else {
                printf("EOF\n");
            }   
        } else {
            printf("timeout\n");
        }
        gettimeofday(&cur, NULL);
    } while (rval > 0 && len > 0 && cmptimestamp(end,cur) > 0);

    return 0;
}

Обратите внимание, что это не определяет клавиши, когда вы их нажимаете, только после того, как вы нажали RETURN или закрыли стандартный ввод.

Это прекрасно работает, однако у меня есть несколько вопросов: 1) Как бы выглядел этот код, если бы задержка была разделена на значение, подобное delayInSeconds, которое, возможно, даже принимало бы нецелочисленные значения? 2) Как насчет неблокирующего стандартного ввода? Когда я разблокирую свой стандартный ввод, символы печатаются, но программа никогда не останавливается / «тайм-аут» никогда не печатается.

gurkensaas 24.11.2022 21:08

Думаю, это правильное направление. однако: 1 - Вы, вероятно, хотите break; по ошибке. 2 - ничто в вопросе не говорит, что мы можем предположить, что через 10 секунд закрывается и мы достигли EOF - вы также, вероятно, захотите использовать повторное открытие стандартного ввода с O_NONBLOCKread(2) вместо getchar(), чтобы избежать каких-либо эффектов буферизации stdio.

root 24.11.2022 21:12

@gurkensaas для переменной задержки, поместите часть секунд в delay.tv_sec и часть микросекунд в delay.tv_usec и добавьте те же суммы к end (вам нужно будет проверить, больше ли end.tv_usec 1000000, и если да, вычтите это количество и добавьте 1 к end.tv_sec Если для разблокированного стандартного ввода вы имеете в виду передачу ввода, он просто прочитает все и остановится, когда будет достигнут конец.

dbush 25.11.2022 04:14

@root Проверка rval > 0 обрабатывает как тайм-аут, так и ошибку от select, а проверка c != 1 обрабатывает ошибки от getchar.

dbush 25.11.2022 04:16

Нет, это не так.

root 25.11.2022 05:41

Если ввод не содержит \n, то в какой-то момент getchar() заблокируется через 10 секунд. Или, если stdin постоянно записывается (навсегда), цикл while может не завершиться через 10 секунд или вообще не завершиться. Если переключение контекста во время printf() происходит в течение 10-секундной отметки, следующая итерация do передаст отрицательное значение delay и напечатает ошибку. Если после \n в буфере stdio есть что-то, оно не будет напечатано на следующей итерации, если select() столкнется с ошибкой/тайм-аутом.

root 25.11.2022 23:42

@root хорошо, я не учел возможное бесконечное чтение на стандартный ввод, а также то, что потоки, отличные от переданного файла или терминала (т. Е. Сокета или какого-либо другого процесса), могут прекратить отправку данных без EOF. Обновлено, чтобы сделать стандартный ввод неблокирующим и удалить внутренний цикл, а также добавить проверку истечения времени перед вызовом select.

dbush 26.11.2022 01:41
Ответ принят как подходящий

Вот предложение, которое соответствует моей интерпретации ваших требований:

  • Программа считывает любые данные, введенные (или иным образом) при стандартном вводе, в течение 10 секунд (останавливаясь, если вам удается ввести 2047 символов — что, вероятно, означает, что ввод поступает из файла или конвейера).
  • Через 10 секунд он печатает все, что собрал.
  • Следовательно, вызов alarm() устанавливает будильник на целое число секунд, и система генерирует сигнал SIGALRM, когда время истекло. Сигнал тревоги прерывает системный вызов read(), даже если данные не были прочитаны.
  • Программа останавливается без печати при получении сигналов.
  • Если сигнал является одним из SIGINT, SIGQUIT, SIGHUP, SIGPIPE или SIGTERM, он останавливается, ничего не печатая.
  • Он возится с настройками терминала, чтобы ввод не буферизовался. Это позволяет избежать его зависания. Это также гарантирует, что системные вызовы не будут перезапущены после получения сигнала. Это может не иметь значения в Linux; при использовании signal() в macOS Big Sur 11.7.1 ввод продолжался после сигнала будильника, что не помогло — использование sigaction() дает вам лучший контроль.
  • Он делает все возможное, чтобы режим терминала восстанавливался при выходе, но если вы отправите неподходящий сигнал (не один из тех, что в списке выше, или SIGALRM), у вас будет терминал в неканоническом (сыром) режиме. Это приводит к путанице, в общем.
  • Программу легко модифицировать так, чтобы:
    • ввод не повторяется драйвером терминала;
    • символы повторяются программой по мере их поступления (но будьте осторожны при редактировании символов);
    • сигналы не генерируются клавиатурой;
    • поэтому он не работает со стандартными атрибутами входного терминала, если это не терминал.

Код

/* SO 7450-7966 */
#include <ctype.h>
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <termios.h>
#include <unistd.h>

#undef sigemptyset      /* MacOS has a stupid macro that triggers -Wunused-value */

static struct termios sane;

static void stty_sane(void)
{
    tcsetattr(STDIN_FILENO, TCSANOW, &sane);
}

static void stty_raw(void)
{
    tcgetattr(STDIN_FILENO, &sane);
    struct termios copy = sane;
    copy.c_lflag &= ~ICANON;
    tcsetattr(STDIN_FILENO, TCSANOW, &copy);
}

static volatile sig_atomic_t alarm_recvd = 0;

static void alarm_handler(int signum)
{
    signal(signum, SIG_IGN);
    alarm_recvd = 1;
}

static void other_handler(int signum)
{
    signal(signum, SIG_IGN);
    stty_sane();
    exit(128 + signum);
}

static int getch(void)
{
    char c;
    if (read(STDIN_FILENO, &c, 1) == 1)
        return (unsigned char)c;
    return EOF;
}

static void set_handler(int signum, void (*handler)(int signum))
{
    struct sigaction sa = { 0 };
    sa.sa_handler = handler;
    sigemptyset(&sa.sa_mask);
    sa.sa_flags = 0;    /* No SA_RESTART! */
    if (sigaction(signum, &sa, NULL) != 0)
    {
        perror("sigaction");
        exit(EXIT_FAILURE);
    }
}

static void dump_string(const char *tag, const char *buffer)
{
    printf("\n%s [", tag);
    int c;
    while ((c = (unsigned char)*buffer++) != '\0')
    {
        if (isprint(c) || isspace(c))
            putchar(c);
        else
            printf("\\x%.2X", c);
    }
    printf("]\n");
}

int main(void)
{
    char buffer[2048];

    stty_raw();
    atexit(stty_sane);
    set_handler(SIGALRM, alarm_handler);
    set_handler(SIGHUP, other_handler);
    set_handler(SIGINT, other_handler);
    set_handler(SIGQUIT, other_handler);
    set_handler(SIGPIPE, other_handler);
    set_handler(SIGTERM, other_handler);
    alarm(10);

    size_t i = 0;
    int c;
    while (i < sizeof(buffer) - 1 && !alarm_recvd && (c = getch()) != EOF)
    {
        if (c == sane.c_cc[VEOF])
            break;
        if (c == sane.c_cc[VERASE])
        {
            if (i > 0)
                i--;
        }
        else
            buffer[i++] = c;
    }
    buffer[i] = '\0';

    dump_string("Data", buffer);
    return 0;
}

Сборник:

gcc -O3 -g -std=c11 -Wall -Wextra -Werror -Wmissing-prototypes -Wstrict-prototypes -fno-common tensec53.c -o tensec53 

Нет ошибок (или предупреждений, но предупреждения конвертируются в ошибки).

Анализ

  • Строка #undef удаляет любое макроопределение sigemptyset(), оставляя компилятору вызов фактической функции. Стандарт C требует, чтобы это работало (§7.1.4 ¶1). В macOS используется макрос #define sigemptyset(set) (*(set) = 0, 0), и GCC небезосновательно жалуется на то, что «правый операнд выражения запятой не имеет эффекта». Альтернативный способ исправить это предупреждение — проверить возвращаемое значение из sigemptyset(), но это, возможно, более глупо, чем макрос. (Да, я недоволен этим!)
  • В переменную sane записывается значение атрибутов терминала при запуске программы — оно устанавливается вызовом tcgetattr() в stty_raw(). Код гарантирует, что sane установлен перед активацией любого кода, который будет вызывать sttr_sane().
  • Функция stty_sane() сбрасывает атрибуты терминала в нормальное состояние, которое действовало при запуске программы. Он используется atexit(), а также обработчиками сигналов.
  • Функция stty_raw() получает исходные атрибуты терминала, делает их копию, изменяет копию, чтобы отключить каноническую обработку (дополнительные сведения см. в разделе Канонический и неканонический ввод терминала) и устанавливает измененные атрибуты терминала.
  • Стандартный C говорит, что вы не можете сделать ничего в функции обработчика сигнала, кроме как установить переменную volatile sig_atomic_t, вызвать signal() с номером сигнала или вызвать одну из функций выхода. POSIX намного мягче — см. Как избежать использования printf() в обработчике сигнала?Подробнее.
  • Есть два обработчика сигналов, один для SIGALRM и один для других перехваченных сигналов.
  • alarm_handler() игнорирует дальнейшие сигналы тревоги и записывает, что он был активирован.
  • other_handler() игнорирует дальнейшие сигналы того же типа, сбрасывает атрибуты терминала в нормальное состояние и завершает работу со статусом, используемым для сообщения о том, что программа была завершена сигналом (см. POSIX shell Статус выхода для команд).
  • Функция getch() считывает один символ из стандартного ввода, сопоставляя ошибки с EOF. Приведение гарантирует, что возвращаемое значение будет положительным, как это делает getchar().
  • Функция set_handler() использует sigaction() для установки обработки сигнала. Использование signal() в обработчиках сигналов немного лениво, но адекватно. Это гарантирует, что бит SA_RESTART не установлен, поэтому, когда сигнал прерывает системный вызов, он возвращается с ошибкой, а не продолжает работу.
  • Функция dump_string() записывает строку с любыми непечатаемыми символами, кроме пробелов, которые сообщаются как шестнадцатеричный escape.
  • Функция main() настраивает терминал, обеспечивает сброс состояния терминала при выходе (atexit() и вызовы set_handler() с аргументом other_handler) и устанавливает сигнал тревоги на 10 секунд.
  • Цикл чтения предотвращает переполнение буфера и останавливается при получении аварийного сигнала или обнаружении EOF (ошибки).
  • Поскольку каноническая обработка отключена, редактирование строк невозможно. Тело цикла обеспечивает примитивное редактирование строк — оно распознает символ стирания (обычно backspace '\b', иногда delete '\177') и символ EOF и обрабатывает их соответствующим образом, в противном случае добавляя ввод в буфер.
  • Когда цикл завершается, обычно из-за срабатывания будильника, он завершает строку нулевым значением, а затем вызывает dump_string() для вывода того, что было введено.
  • Если вам нужны интервалы в доли секунды, вам нужно будет использовать функции POSIX timer_create(), timer_delete(), timer_settime() (и, возможно, timer_gettime() и timer_getoverrun()), которые принимают значения struct timespec для значений времени. Если они недоступны, вы можете использовать вместо них устаревшие функции setitimer() и getitimer(). Шаг timer_create() позволяет вам указать, какой сигнал будет отправлен по истечении таймера — в отличие от alarm() и setitimer(), которые отправляют заранее определенные сигналы.

Функции и заголовки POSIX:

Этот код доступен в моем репозитории SOQ (вопросы о переполнении стека) на GitHub в виде файла tensec53.c в подкаталоге src/so-7450-7966.

Jonathan Leffler 25.11.2022 19:21

Я бы добавил к анализу одну вещь: наличие сигнала тревоги — это то, что разблокирует read() звонок в getch(). Я бы также установил MIN и/или TIME вместо того, чтобы использовать специальные символы терминала по умолчанию.

root 26.11.2022 00:15

@root — спасибо; Я сделал некоторый акцент на роли alarm(), а также добавил информацию о времени долей секунды для будильника с помощью timer_settime() и др. (или, используя устаревшую функцию, с помощью setitimer()).

Jonathan Leffler 26.11.2022 04:08

Другие вопросы по теме