Search code examples
cnewlinefile-handlingfeof

How do I read the last line of a text file in a C program?


I'm trying to study the C language and basically what I want to do is read a file and put it into a struct I created, and then later I'll be doing other things with the struct, but I want to get through the first part first. Let's say that I have a text file called captains.txt and the contents are:

picard 95
janeway 90
pike 15

(note that the last line is just 'pike 15')

So I created a program that's like this:

#include <stdio.h>
#include <stdlib.h> //for exit()
#include <string.h>
#include <ctype.h>

struct captain
{
    char capName[10];
    int number;

};
typedef struct captain captain;

int main()
    {

        FILE* file = fopen("captain.txt","r");
        if (file == NULL)
        {
            printf("\nerror opening file");
            exit(1);
        }

        else{
            printf("\nfile is opened");
        }

        char buffer[50];
        fgets(buffer,50,file);

        while (!feof(file))
        {
            captain c;
            sscanf(buffer, "%s %d", &c.capName, &c.number);
            printf("\nc captain is: %s %d", c.capName, c.number);
            fgets(buffer,50,file);
        }
        fclose(file);

        return 0;
}

The output on my console is

file is opened
c captain is: picard 95
c captain is: janeway 90
Process returned 0 (0x0)   execution time : 0.006 s
Press any key to continue.

Hence Captain Pike is missing in space... almost literally because when I add a new line to the text file that it becomes like this:

picard 95
janeway 90
pike 15

(note the newline after 'pike 15')

Then my output becomes correct. So I know that my program doesn't account for the lack of a newline at the end of the file... so how do I solve this?


Solution

  • Compare these two programs, one (mis)using feof() and one not using it at all. The first corresponds closely to the code in the question — it ignores the return value from fgets() to its detriment. The second only tests the return value from fgets(); it has no need to use feof().

    eof53.c

    #include <stdio.H>
    
    int main(void)
    {
        char buffer[256];
    
        fgets(buffer, sizeof(buffer), stdin);
        while (!feof(stdin))
        {
            printf("[%s]\n", buffer);
            fgets(buffer, sizeof(buffer), stdin);
        }
        return 0;
    }
    

    eof71.c

    #include <stdio.H>
    
    int main(void)
    {
        char buffer[256];
    
        while (fgets(buffer, sizeof(buffer), stdin) != NULL)
            printf("[%s]\n", buffer);
        return 0;
    }
    

    Given a data file abc containing 3 bytes — 0x41 ('A'), 0x42 ('B'), 0x43 ('C') and no newline, I get the following results:

    $ eof53 < abc
    $ eof71 < abc
    [ABC]
    $
    

    This was tested on MacOS Big Sur 11.6.6.

    Note that fgets() does not report EOF (by returning a null pointer) when reading the (only) incomplete line, but empirically, feof() does report EOF — correctly, since the file input has ended, even though fgets() did return a string (but not a line) of data.

    As explained in the canonical Q&A while (!feof(file)) is always wrong!, using feof() rather than testing the return value from the I/O functions leads to bad results.