1

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?

Jonathan Leffler
  • 730,956
  • 141
  • 904
  • 1,278
Emil Lang
  • 35
  • 6
  • (Combined with your file not having a trailing newline, which makes it technically an invalid text file; `fgets()` hits end of file looking for it, and thus your loop never runs for the string it reads) – Shawn May 26 '22 at 15:29
  • Hey Shawn, could you explain what you mean by an invalid text file? Let's say you have a user who just wants to make a text file going 'name value' so it looks like 2 columns going down the window. They won't hit return again after their last line and end it there. – Emil Lang May 26 '22 at 15:32
  • 1
    From the POSIX definition of a text file: *The only difference between text and binary files is that text files have lines of less than `{LINE_MAX}` bytes, with no `NUL` characters, each terminated by a ``. The definition allows a file with a single ``, or a totally empty file, to be called a text file. If a file ends with an incomplete line it is not strictly a text file by this definition.* – Shawn May 26 '22 at 15:42
  • Most decent editors will automatically add one to the end of the file if not already present. – Shawn May 26 '22 at 15:43
  • That's incredibly odd but illuminating. I'm simply opening Windows notepad and keying in some test text into it to use for my question above. At least I'll have something to discuss with my friends. – Emil Lang May 26 '22 at 15:56
  • 2
    These days it's very common to have files with missing final newlines. (In particular, Windows users don't care about the POSIX definition of a text file. :-) ) Most text-file-manipulating programs handle missing final newlines just fine, and those that have trouble, are arguably buggy. – Steve Summit May 26 '22 at 15:59

1 Answers1

1

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.

Jonathan Leffler
  • 730,956
  • 141
  • 904
  • 1,278