Thread: fgets reading remains from stdin

  1. #1
    Registered User
    Join Date
    May 2012
    Location
    Brazil
    Posts
    58

    fgets reading remains from stdin

    Hello there,

    I have a function which reads the name of a file using fgets, like this:
    Code:
    void read_file (struct matrix * const a){
        char f_name [122];
        size_t f_namelen;
        
        printf("Type filename:\n");
        
        if (fgets(f_name, sizeof(f_name), stdin) == NULL){
            fprintf(stderr, "No filename read.\n");
            exit(EXIT_FAILURE);
        }
        
        f_namelen = strcspn(f_name, "\r\n");
        
        if (f_namelen < 1){
            fprintf(stderr, "No filename read.\n");
            exit(EXIT_FAILURE);
        }
    (...)
    This is the beginning of the function. It then checks for more conditions and opens the file.

    So when the program starts, it calls that function and the file is opened without problems. Then a menu is shown, and it uses scanf to scan the option and then perform some operations. If the user types 3, it should call that function again to open another file.

    In this case, the '\n' passed when the user presses ENTER is still in stdin, so fgets receives an \n and "thinks" it's the filename, so the function fails when checking if f_namelen < 1.

    The option for the switch is read like this:
    Code:
    scanf(" %d", &opt);
    Do you guys have any ideas about how to solve this problem?

    Thanks in advance.
    Last edited by koplersky; 11-22-2012 at 10:54 PM.

  2. #2
    and the hat of int overfl Salem's Avatar
    Join Date
    Aug 2001
    Location
    The edge of the known universe
    Posts
    39,667
    Don't mix fgets() and scanf() would be one approach.
    That is, fgets() everything, then sscanf() what you want out of the buffer.

    Or if you do mix them, then you need to do a bit of cleanup when you switch from scanf to fgets.
    FAQ > Flush the input buffer - Cprogramming.com
    If you dance barefoot on the broken glass of undefined behaviour, you've got to expect the occasional cut.
    If at first you don't succeed, try writing your phone number on the exam paper.

  3. #3
    Ticked and off
    Join Date
    Oct 2011
    Location
    La-la land
    Posts
    1,728
    Quote Originally Posted by koplersky View Post
    I have a function which reads the name of a file using fgets, like this
    I warmly recommend splitting the functionality into smaller functions:
    Code:
    /* Read a matrix using the specified file handle.
     * Returns 0 if success, nonzero error code otherwise.
    */
    int read_matrix(FILE *const in, struct matrix *const m)
    {
        int rows, cols, r, c;
    
        /* First two integers define matrix rows and cols. */
        if (fscanf(in, " %d %d", &rows, &cols) != 2)
            return -1; /* Invalid file contents (not two integers). */
    
        /* Bad dimensions? */
        if (rows < 1 ||..cols < 1)
            return -2; /* Bad matrix dimensions. */
    
        /*
         * TODO: Initialize and allocate matrix m.
        */
    
        /* Read matrix elements in row-major order. Assume elements of double type. */
        for (r = 0; r < rows; r++)
            for (c = 0; c < cols; c++)
                if (fscanf(in, " %lf", m->element[r * cols + c]) != 1)
                    return -3; /* Bad matrix data. */
    
        /* Note: there may be extra data on this line in input.
         * If there is only one matrix per file, it does not matter.
         * Otherwise, you might consider skipping the rest of the line with
         *
         *  while (1) {
         *      c = fgetc(in);
         *      if (c == '\n' || c == '\r' || c == EOF)
         *          break;
         *      if (!isspace(c))
         *          return -4; /* Extra data on line containing last element */
         *  }
         *
         * which consumes the rest of the line containing the last element,
         * checking that there is nothing but whitespace.
         * This way you also check that there is a newline or end-of-file
         * after the last element, instead of garbage/extra data:
         *      2 2 1 2 3 4
         *      5
         * would be accepted (as a 2-by-2 matrix, only the first line read,
         * the 5 not read by this function), but
         *      2 2 1 2 3 4 5
         * would be rejected since there is a 5 following the matrix data.
        */
    
        /* Success. */
        return 0;
    }
    I would even write a small helper function to read a new line from standard input, removing leading and trailing whitespace:
    Code:
    #include <stdarg.h>
    #include <stdio.h>
    #include <ctype.h>
    
    /* Read a line from standard input,
     * printf()ing the parameters to standard error (unless NULL).
     * Return NULL if end of input or read error.
    */
    const char *prompt(const char *const msg, ...)
    {
        static char  buffer[1024]; /* Static line buffer. */
        char        *line, *ends;
        va_list  args;
    
        /* Prompt specified? */
        if (msg) {
            va_start(args, msg);
            vfprintf(stderr, msg, args);
            va_end(args);
        }
    
        /* Read a line of input. */
        line = fgets(buffer, sizeof buffer, stdin);
    
        /* No more input? */
        if (!line)
            return NULL;
    
        /* Trim leading whitespace. */
        while (isspace(*line))
            line++;
    
        /* Find the end of the line, */
        ends = line + strlen(line);
    
        /* and backtrack whitespace characters. */
        while (ends > line && isspace(ends[-1]))
            ends--;
    
        /* Trim the line. */
        *ends = '\0';
    
        /* Because buffer has static storage,
         * it exists between calls. New calls will
         * overwrite the previous value. */
        return line;
    }
    Because it uses variable argument lists (stdargs; ... in function parameter list, va_start(), va_end(), vfprintf()), you can supply it with NULL, or with same parameters you'd supply to printf(), to print out the prompt.

    For a menu, I'd write a small function that would try to parse the menu selections, and nonnegative integer matching the input, or -1 if unrecognized:
    Code:
    int option(const char *const line)
    {
        /* NULL line? */
        if (!line)
            return -1;
    
        /* Check if it is a single nonnegative integer? */
        {
            int   value;
            char  dummy;
    
            if (sscanf(line, " %d %c", &value, &dummy) == 1 && value >= 0)
                return value; /* Yes, return it. */
        }
    
        /* Perhaps a word? "one" == "1": */
        if (!strcmp(line, "one"))
            return 1;
    
        /* "quit" == "exit" == "0": */
        if (!strcmp(line, "quit") || !strcmp(line, "exit"))
            return 0;
    
        /* Not recognized. */
        return -1;
    }
    The important thing is you can write and test each of these smaller functions separately. That way there are less places where you might have errors or bugs, so writing the code (and reading it later!) is much easier.

    I think you can see how to use lineptr = prompt("Please select ...:\n") to prompt the user for a menu option, then option(lineptr) to check if that option is valid (and if so, which one it is).

    To prompt the user for a matrix name, you'd again use lineptr = prompt("Matrix file name?\n") , then handle = fopen(lineptr, "r"), and if successful, result = read_matrix(handle, matrixptr), followed by fclose(handle) and checking whether a suitable matrix was read (result == 0, and matrixptr is of suitable dimensions).

  4. #4
    Registered User
    Join Date
    May 2012
    Location
    Brazil
    Posts
    58
    Thank you both, it's now working fine.

    Nominal Animal, could you tell me what's the meaning of the "..." in the arguments of the prompt function?

  5. #5
    TEIAM - problem solved
    Join Date
    Apr 2012
    Location
    Melbourne Australia
    Posts
    1,907
    Quote Originally Posted by koplersky View Post
    Thank you both, it's now working fine.

    Nominal Animal, could you tell me what's the meaning of the "..." in the arguments of the prompt function?
    Have a look at NA's links to stdarg & va_start(), va_end(), vfprintf()
    Fact - Beethoven wrote his first symphony in C

  6. #6
    Ticked and off
    Join Date
    Oct 2011
    Location
    La-la land
    Posts
    1,728
    Quote Originally Posted by koplersky View Post
    what's the meaning of the "..."
    "Plus possibly further parameters."

    The stdarg facilities provided by <stdarg.h> provide a way to iterate over those unspecified parameters.

    A variable of va_list type keeps track of the position in the parameter list. You initialize it, also specifying the last known parameter, by calling va_init(). The list starts after the named parameter, and does not include the named parameter.

    In my code, instead of trying to find out what those parameters might be, I just pass them to the vfprintf() function, which is a printf variant designed to work with those variable argument lists. Thus, my function works just like printf() does: you can supply it with a format string, and the stuff to be so formatted.

    You only need to be careful to always remember to call va_end() on every list variable you have called va_init() on, or otherwise your program may not compile, or may behave in weird and interesting but undesirable ways. (Bugs that seem to appear out of nowhere, sometimes in totally different parts of the code, much later in the execution. Stack corruption is annoying to debug, so be careful.)

    This kind of small details makes sometimes a big difference. Here, consider how annoying it would be if you had to first allocate a char array, then snprintf() the stuff into it, then pass the array to this function, if you wanted a formatted prompt. Instead, we can use the facilities provided, and make the function a bit smarter, but much easier to use.

    (I did agonize a bit whether to use just a fixed string, but I decided this is the perfect opportunity to softly introduce variable argument lists, without getting into the details.)

  7. #7
    Registered User
    Join Date
    May 2012
    Location
    Brazil
    Posts
    58
    That was my first contact with those functions and I weren't sure about how they worked.

    After seeing an example of a function like prompt being used, I now understand it.

    Thank you!

Popular pages Recent additions subscribe to a feed

Similar Threads

  1. fgets with newline in the middle of the stdin
    By sebby64 in forum C Programming
    Replies: 2
    Last Post: 11-27-2010, 01:27 PM
  2. fgets() from stdin and strcat()
    By rocketman03 in forum C Programming
    Replies: 9
    Last Post: 11-01-2008, 01:31 PM
  3. Problems with fgets not accepting input from stdin
    By k2712 in forum C Programming
    Replies: 7
    Last Post: 08-25-2007, 11:33 PM
  4. fgets() for stdin not working
    By Yasir_Malik in forum C Programming
    Replies: 2
    Last Post: 03-05-2005, 12:12 PM
  5. using stdin in fgets
    By Unregistered in forum C Programming
    Replies: 2
    Last Post: 04-14-2002, 09:14 PM