0

My Problem:

My goal is to read a fixed-length string from the stdin, store it into a buffer and then immediately proceed the execution of the following code but neither fgets() nor scanf() stop to waiting for to get more input, even if the fixed amount of characters to read is fixed -> scanf("%5s",c); and fgets(b,6,stdin);.

I have written that example to illustrate the issue:

#include <stdio.h>
#include <string.h>

int main(void)
{
    char b[6];
    char c[6];
    
    printf("Enter a string of 5 characters (b):\n");
    fgets(b, sizeof(b), stdin);   
    getchar();                    // catch the left newline in stdin because in 
                                  // b was not enough space.
    
    printf("\nEnter a string of 5 characters (c):\n");    
    scanf("%5s", c);
    //getchar();                  // catch the left newline. important if there is any 
                                  // read operation thereafter.
    printf("\nb: %s\n", b);
    printf("c: %s\n", c);

    return 0;
}

Execution:

/a.out
Enter a string of 5 characters (b):
hello

Enter a string of 5 characters (c):
world

b: hello
c: world

Both, fgets() and scanf() wait for the newline. As a side note: In case of scanf() with format specifier %s, any white space would stop the consuming of the directive; In case of scanf() with format specifier %[ and a negated scanset %[^N], the occurence of N would stop the consuming.

Either way it is waiting for more input, although specified to read only 5 characters.

How do I read a string without waiting for more input?


My Research:

I´ve found a way to achieve what I want but it is a pretty large workaround: I could catch each character separately and use a getch() derivate for Linux (because I work on Linux and conio.h isn´t a topic here) which are provided in the answers to What is the equivalent to getch() & getche() in Linux? in a for-loop and thereafter assign the terminating null character to the last element like f.e.:

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

static struct termios old, current;

/* Initialize new terminal i/o settings */
void initTermios(int echo) 
{
  tcgetattr(0, &old); /* grab old terminal i/o settings */
  current = old; /* make new settings same as old settings */
  current.c_lflag &= ~ICANON; /* disable buffered i/o */
  if (echo) {
      current.c_lflag |= ECHO; /* set echo mode */
  } else {
      current.c_lflag &= ~ECHO; /* set no echo mode */
  }
  tcsetattr(0, TCSANOW, &current); /* use these new terminal i/o settings now */
}

/* Restore old terminal i/o settings */
void resetTermios(void) 
{
  tcsetattr(0, TCSANOW, &old);
}

/* Read 1 character - echo defines echo mode */
char getch_(int echo) 
{
  char ch;
  initTermios(echo);
  ch = getchar();
  resetTermios();
  return ch;
}

/* Read 1 character without echo */
char getch(void) 
{
  return getch_(0);
}

/* Read 1 character with echo */
char getche(void) 
{
  return getch_(1);
}

int main(void)
{
    char b[6];
    int c;
    
    printf("Enter a string of 5 characters (b):\n");

    for(int i = 0; i < 5; i++)
    {
        if((c = getch_(1)) == EOF)
        {
            if(ferror(stdin))
            {

            }
            else if(feof(stdin))
            {
                fprintf(stderr,"End of file reached!"); 
            }          
        }
        else
        {
            b[i] = c;
        }
    }

    b[5] = '\0';
    
    printf("\n\n");

    puts(b);

    return 0;
}

But this method is a pretty big workaround. Is there a way to achieve that in a simpler way?

Community
  • 1
  • 1
  • 1
    Maybe set `stdin` to unbuffered with [`setvbuf()`](https://pubs.opengroup.org/onlinepubs/9699919799/functions/setvbuf.html) (untested)? – pmg Apr 07 '20 at 10:34
  • Use a library like ncurses – stark Apr 07 '20 at 10:44
  • 1
    Like @pmg indicated, I think this has nothing to do with `scanf()` or `fgets()`. Your input stream `stdin` is commonly set to "line buffered". Both function don't see *any* input before you type a newline. The work-around you found is a kind of complicated way to change the buffering, like pmg proposed much simpler. – the busybee Apr 07 '20 at 11:17
  • A simply exercise to detect if input is line buffered: Try "X" backspace "Y" Enter. Does `fgets()` read `"Y\n"` or `"X\bY\n"`? If input is _line buffered_, `fgets()` gets nothing until the line is complete with its trailing `'\n'`. – chux - Reinstate Monica Apr 07 '20 at 13:35

0 Answers0