Как заставить программу на Си остановить цикл от любого пользовательского ввода?

Я хотел бы написать небольшую программу на C, которая выполняет бесконечный цикл, пока пользователь не нажмет клавишу на клавиатуре (то есть: в буфере stdin есть символ). Я столкнулся с проблемой разрыва цикла при вводе данных пользователем. Я пытался использовать fgetc но это не ведет себя, как ожидалось. Код ниже ожидает пользовательского ввода, а не работает до пользовательского ввода.

Образец кода C:

while((c=fgetc(stdin) == EOF) {
  /* Does stuff for infinite loop here */
  printf("Example work in the loop\n");
}
printf("Out of the loop!\n");

Как мне написать цикл, который выполняется до вмешательства пользователя? Нажатие любой клавиши или определенной клавиши может быть триггером вмешательства.

Примечание 1: я пишу это для консоли Unix в случае решений для конкретной платформы

Примечание 2: не предлагать Ctrl + C/X/Z как пользовательский триггер вмешательства

1 ответ

Решение

Кажется, это работает для меня:

#include <fcntl.h>
#include <stdio.h>
#include <sys/types.h>
#include <sys/uio.h>
#include <unistd.h>

static void set_non_blocking(int fd)
{
    int flags  = fcntl(fd, F_GETFL, 0 );
    flags |= O_NONBLOCK;
    flags = fcntl(fd, F_SETFL, flags);
}


int main(int argc, char ** argv)
{
    int fd = fileno(stdin);
    char buf[10];

    set_non_blocking(fd);

    while (read(fd, buf, sizeof buf) < 0) {
        perror("read");
        sleep(1);
    }
    return 0;
}

или вы могли бы использовать select:

int main(int argc, char ** argv)
{
    int fd = fileno(stdin);
    struct timeval tv = {0,0};
    fd_set fdset;
    int s;

    do {
        sleep(1);
        FD_ZERO(&fdset);
        FD_SET(fd, &fdset);

    } while ((s = select(fd+1, &fdset, NULL, NULL, &tv)) == 0);

    if (s < 0) {
        perror("select");
    }
    return 0;
}

Опрос тоже работает:-)

int main(int argc, char ** argv)
{
    struct pollfd pfd;
    int s;

    pfd.fd = fileno(stdin);
    pfd.events = POLLRDNORM;

    while ((s = poll(&pfd, 1, 0)) == 0) {
        perror("polling");
        sleep(1);
    }
    if (s < 0) {
        perror("poll");
    }
    return 0;
}

Последний способ - установить терминал в "сырой" режим. Обратите внимание, что это нарушает вывод на терминал (по крайней мере на моем на OS-X), так как \r становится необходимым после \n. Обратите также внимание, что его необходимо отменить в конце (завершающий tcsetattr вызов). Это единственный, который не нуждается в \n (т.е. подойдет любое нажатие клавиши)

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


static void set_non_blocking(int fd)
{
    int flags = fcntl(fd, F_GETFL, 0) | O_NONBLOCK;

    if (fcntl(fd, F_SETFL, flags) < 0) {
        perror("fcntl");
        exit(EXIT_FAILURE);
    }
}


int main(int argc, char ** argv)
{
    struct termios params;
    struct termios params_orig;
    char buf[10];
    int fd = fileno(stdin);

    if (tcgetattr(fd, &params) < 0) {
        perror("tcgetattr");
        exit(EXIT_FAILURE);
    }
    params_orig = params;

    cfmakeraw(&params);

    if (tcsetattr(fd, TCSANOW, &params) < 0) {
        perror("tcsetattr");
        exit(EXIT_FAILURE);
    }
    set_non_blocking(fd);

    while (read(fd, buf, sizeof buf) < 0) {
        perror("\rread");
        sleep(1);
    }

    (void) tcsetattr(fd, TCSANOW, &params_orig);
    return 0;
}
Другие вопросы по тегам