Home > Net >  how to verify fgets() read a line and handle errors
how to verify fgets() read a line and handle errors

Time:07-29

I am using fgets to read a file line by line

    char buffer[4096];

    while (fgets(buffer, sizeof(buffer), file__1) != NULL) {
           
           fprintf(file__2, "%s", buffer);
    }

However the man page of fgets() says this under the Examples section

           while (fgets(line, line_max   1, fp) != NULL) {
               // Verify that a full line has been read ...
               // If not, report an error or prepare to treat the
               // next time through the loop as a read of a
               // continuation of the current line.
               ...
               // Process line ...
               ...
           }

My question is, upon fgets failing how could i "Verify that a full line has been read"?

CodePudding user response:

This wouldn't be a failure on the part of fgets(); the control flow wouldn't go into the loop if fgets failed and returned a null pointer.

The need to handle full-line checking arises if you are reading a line with fgets and the line happens to be longer than the size passed to fgets() in argument 2. In this situation, fgets() is returning the passed buffer and is a "success".

You can check for the problem by checking whether the last character in the string is a newline. You can then either abort or handle it somehow.

So something like this would handle the check:


#include <stdio.h>
#include <string.h>
#define LINE_MAX 5
int main() {
  char line[LINE_MAX   1];
  while (fgets(line, LINE_MAX   1, stdin) != NULL) {
    size_t length = strlen(line);
    if (length && line[length - 1] != '\n') {
      printf("line max (%d) reached\n", LINE_MAX);
      return 1;
    }
  }
}

CodePudding user response:

how could i "Verify that a full line has been read"?

In cases where fgets returns NULL, it means that either

  • end-of-file was encountered before reading a single character (not even a newline character), or

  • an error occurred on the stream.

Therefore, when fgets returns NULL, you should always assume that a full line has not been read.

However, when fgets does not return NULL, then it means that the function call to fgets was successfull. But this does not necessarily mean that a full line has been read. It is possible that fgets successfully filled the buffer, but that the line was too long to fit in the buffer. Therefore, the easiest way to determine whether a full line was read is to check whether the string returned by fgets contains a newline character, for example by using the function strchr.

Even if a newline character is not found, that does not necessarily mean that a full line has not been read. Although POSIX defines a line to end with a newline character, it is possible that you are reading a text file which does not follow this rule. It is possible that you are reading a text file whose last line does not have a newline character, so that you encounter end-of-file without a newline character immediately beforehand. In that case, it is probably appropriate to also consider that line to be "a full line", even if you did not encounter a newline character before reaching end-of-file.

This scenario of encountering end-of-file without a newline character immediately beforehand is also possible when dealing with user input. For example, on Linux, the user can press CTRL D to enter end-of-file with the keyboard. On Microsoft, you can do the same with CTRL Z.

For this reason, in cases in which you cannot find a newline character, it is probably appropriate to check the end-of-file indicator of the stream using the function feof, and to ignore the missing newline character when the end-of-file indicator of the stream is set. Only when the end-of-file indicator is not set should an error message be printed that the line is too long to fit in the buffer.

In order to read a text file line-by-line and ensure that a full line was always read, I recommend the following code:

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

FILE *fp;
char line[512], *p;

int main()
{
    //open file
    fp = fopen( "input.txt", "r" );
    if ( fp == NULL )
    {
        fprintf( stderr, "Error opening file!\n" );
        exit( EXIT_FAILURE );
    }

    //read one line per loop iteration
    for (;;) //infinite loop, equivalent to while(1)
    {
        //attempt to read one line of input
        if ( fgets( line, sizeof line, fp ) == NULL )
        {
            //check for stream error
            if ( ferror(fp) )
            {
                fprintf( stderr, "Stream error!\n" );
                exit( EXIT_FAILURE );
            }

            //we must have encountered end-of-file, so break out
            //of the infinite loop without an error message
            break;
        }

        //attempt to find newline character
        p = strchr( line, '\n' );

        if ( p == NULL )
        {
            //a missing newline character should be ignored on
            //end-of-file
            if ( !feof(fp) )
            {
                fprintf( stderr, "Line too long for buffer!\n" );
                exit( EXIT_FAILURE );
            }
        }
        else
        {
            //remove newline character
            *p = '\0';
        }

        //a full line was read, so print it
        puts( line );
    }

    //cleanup
    fclose( fp );
}
  • Related