Checking the stdin buffer if it's empty

前端 未结 4 1547
名媛妹妹
名媛妹妹 2020-12-01 22:18

I am trying to read a number character with character, but I don\'t know if the stdin buffer is empty or not.

My first solution whas to look for \'\\n\' character i

相关标签:
4条回答
  • 2020-12-01 22:21

    There are many ways to check if stdin has input available. The most portable ones are, in that order: select, fcntl and poll.

    Here some snippets on how to do it, case by case.

    #include <stdio.h> /* same old */
    #include <stdlib.h> /* same old */
    #include <time.h> /* struct timeval for select() */
    #include <unistd.h> /* select() */
    #include <poll.h> /* poll() */
    #include <sys/ioctl.h> /* FIONREAD ioctl() */
    #include <termios.h> /* tcgetattr() and tcsetattr() */
    #include <fcntl.h> /* fnctl() */
    
    #define BUFF 256
    
    int chkin_select(void);
    int chkin_poll(void);
    int chkin_ioctl(void);
    int chkin_fcntl(void);
    int chkin_termios(void);
    
    /*
      Simple loops to test varios options of non-blocking test for stdin
    */
    
    int main(void)
    {
        char sin[BUFF]="r";
    
        printf("\nType 'q' to advance\nTesting select()\n");
        while(sin[0]++ != 'q')
        {
            while(!chkin_select())
            {
                printf("nothing to read on select()\n");
                sleep(2);
            }
            fgets(sin, BUFF, stdin);
            printf("\nInput select(): %s\n", sin);
        }
    
        printf("\nType 'q' to advance\nTesting poll()\n");
        while(sin[0]++ != 'q')
        {
            while(!chkin_poll())
            {
                printf("nothing to read poll()\n");
                sleep(2);
            }
            fgets(sin, BUFF, stdin);
            printf("\nInput poll(): %s\n", sin);
        }
    
        printf("\nType 'q' to advance\nTesting ioctl()\n");
        while(sin[0]++ != 'q')
        {
            while(!chkin_ioctl())
            {
                printf("nothing to read ioctl()\n");
                sleep(2);
            }
            fgets(sin, BUFF, stdin);
            printf("\nInput ioctl(): %s\n", sin);
        }
    
        printf("\nType 'q' to advance\nTesting fcntl()\n");
        while(sin[0]++ != 'q')
        {
            while(!chkin_fcntl())
            {
                printf("nothing to read fcntl()\n");
                sleep(2);
            }
            fgets(sin, BUFF, stdin);
            printf("\nInput fcntl: %s\n", sin);
        }
    
        printf("\nType 'q' to advance\nTesting termios()\n");
        while(sin[0]++ != 'q')
        {
            while(!chkin_termios())
            {
                printf("nothing to read termios()\n");
                sleep(2);
            }
            fgets(sin, BUFF, stdin);
            printf("\nInput termios: %s\n", sin);
        }
    
        return EXIT_SUCCESS;
    }
    
    /*
       select() and pselect() allow a program to monitor multiple file
       descriptors, waiting until one or more of the file descriptors become
       "ready" for some class of I/O operation (e.g., input possible).  A
       file descriptor is considered ready if it is possible to perform a
       corresponding I/O operation (e.g., read(2) without blocking, or a
       sufficiently small write(2)).
     */
    int chkin_select(void)
    {
        fd_set rd;
        struct timeval tv={0};
        int ret;
    
        FD_ZERO(&rd);
        FD_SET(STDIN_FILENO, &rd);
        ret=select(1, &rd, NULL, NULL, &tv);
    
        return (ret>0);
    }
    
    /*  poll() performs a similar task to select(2): it waits for one of a
           set of file descriptors to become ready to perform I/O.
    
           The set of file descriptors to be monitored is specified in the fds
           argument, which is an array of structures of the following form:
    
               struct pollfd {
                   int   fd;         // file descriptor //
                   short events;     // requested events //
                   short revents;    // returned events //
               };
    
           The caller should specify the number of items in the fds array in
           nfds.
    */
    int chkin_poll(void)
    {
        int ret;
        struct pollfd pfd[1] = {0};
    
        pfd[0].fd = STDIN_FILENO;
        pfd[0].events = POLLIN;
        ret = poll(pfd, 1, 0);
    
        return (ret>0);
    }
    
    /*
        The ioctl(2) call for terminals and serial ports accepts many
           possible command arguments.  Most require a third argument, of
           varying type, here called argp or arg.
    
           Use of ioctl makes for nonportable programs.  Use the POSIX interface
           described in termios(3) whenever possible.
    */
    int chkin_ioctl(void)
    {
        int n;
        ioctl(STDIN_FILENO, FIONREAD, &n);
        return (n>0);
    }
    
    /*
           fcntl() performs one of the operations described below on the open
           file descriptor fd.  The operation is determined by cmd.
    
           fcntl() can take an optional third argument.  Whether or not this
           argument is required is determined by cmd.  The required argument
           type is indicated in parentheses after each cmd name (in most cases,
           the required type is int, and we identify the argument using the name
           arg), or void is specified if the argument is not required.
    
           Certain of the operations below are supported only since a particular
           Linux kernel version.  The preferred method of checking whether the
           host kernel supports a particular operation is to invoke fcntl() with
           the desired cmd value and then test whether the call failed with
           EINVAL, indicating that the kernel does not recognize this value.
    */
    int chkin_fcntl(void)
    {
        int flag, ch;
    
        flag = fcntl(STDIN_FILENO, F_GETFL, 0); /* save old flags */
        fcntl(STDIN_FILENO, F_SETFL, flag|O_NONBLOCK); /* set non-block */
        ch = ungetc(getc(stdin), stdin);
        fcntl(STDIN_FILENO, F_SETFL, flag); /* return old state */
    
        return (ch!=EOF);
    }
    
    /*
     The termios functions describe a general terminal interface that is provided to control asynchronous communications ports.
     This function doesn't wait for '\n' to return!
     */
    int chkin_termios(void)
    {
        struct termios old, new;
        int ch;
    
        tcgetattr(STDIN_FILENO, &old); /* save settings */
    
        new = old;
        new.c_lflag &= ~ICANON; /* non-canonical mode: inputs by char, not lines */ 
        new.c_cc[VMIN] = 0; /* wait for no bytes at all */
        new.c_cc[VTIME] = 0; /* timeout */
        tcsetattr(STDIN_FILENO, TCSANOW, &new); /* new settings */
    
        ch = ungetc(getc(stdin), stdin); /* check by reading and puking it back */
    
        tcsetattr(STDIN_FILENO, TCSANOW, &old); /* restore old settings */
        return (ch!=EOF);
    }
    

    Try to avoid ioctl and termios, they are too specific, or too low-level. Also, you can't really use feof in a meaningful way with stdin or any FIFO for that matter. You can guarantee the pointer position, and if you try ftell or fseek you will get an error (ask perror).


    References:

    • http://man7.org/linux/man-pages/man2/select.2.html
    • http://man7.org/linux/man-pages/man2/poll.2.html
    • http://man7.org/linux/man-pages/man2/ioctl.2.html
    • http://man7.org/linux/man-pages/man2/fcntl.2.html
    • http://man7.org/linux/man-pages/man3/termios.3.html
    0 讨论(0)
  • 2020-12-01 22:24

    For anyone who comes here from google – easy select solution to check stdin emptyness:

    fd_set readfds;
    FD_ZERO(&readfds);
    FD_SET(STDIN_FILENO, &readfds);
    fd_set savefds = readfds;
    
    struct timeval timeout;
    timeout.tv_sec = 0;
    timeout.tv_usec = 0;
    
    int chr;
    
    int sel_rv = select(1, &readfds, NULL, NULL, &timeout);
    if (sel_rv > 0) {
      puts("Input:");
      while ((chr = getchar()) != EOF) putchar(chr);
    } else if (sel_rv == -1) {
      perror("select failed");
    }
    
    readfds = savefds;
    

    Needs unistd.h, stdlib.h and stdio.h.

    Explanation can be found here.

    UPD: Thanks DrBeco for noticing that select returns -1 on error -- error handling was added.

    Actually, select returns:

    • the number of ready descriptors that are contained in the descriptor sets
    • 0 if the time limit expires
    • -1 if an error occurred (errno would be set)
    0 讨论(0)
  • 2020-12-01 22:31

    There are several soutions:

    poll or select with timeout of 0 - these would return immediately and result is either -1 with errno EAGAIN if no data available or number of descriptors with data (one, since you're checking only stdin).

    ioctl is a swiss army knife of using descriptors. The request you need is I_NREAD:

    if (ioctl(0, I_NREAD, &n) == 0 && n > 0)
        // we have exactly n bytes to read
    

    However the correct solution is to read everything you got (using scanf) as a line, then process the result - and this works good enough with sscanf:

    char buf[80]; // large enough
    scanf("%79s", buf); // read everything we have in stdin
    if (sscanf(buf, "%d", &number) == 1)
        // we have a number
    

    ... as long as you properly handle re-reading, strings that are longer than your buffer, and other real-life complications.

    Edit: removed feof since it is actually used for other things.

    0 讨论(0)
  • 2020-12-01 22:37

    I inspired from this as referenced by @stek29's post on this page and prepared a simple example as follows:

    #include <stdio.h>
    #include <stdlib.h>
    #include <unistd.h>
    
    int main(void)
    {
        fd_set readfds;
        FD_ZERO(&readfds);
    
        struct timeval timeout;
        timeout.tv_sec = 0;
        timeout.tv_usec = 0;
    
        char message[50];
    
        while(1)
        {
            FD_SET(STDIN_FILENO, &readfds);
    
            if (select(1, &readfds, NULL, NULL, &timeout))
            {
                scanf("%s", message); 
                printf("Message: %s\n", message);
            }
    
            printf("...\n");
            sleep(1);
        }
    
        return(0);
    }
    
    0 讨论(0)
提交回复
热议问题