Home > Net >  Write struct into file in c
Write struct into file in c

Time:11-12

I am trying to write struct to file. I have done the part where I defined struct I read text from file and then I saved it in struct now I am on a point where I want to make change in struct: students[i].name and then I want the change to be reflected (write) in file but it has invalid encoding.

file.txt has this structure:

U,Virat Kohli,Virat Kohli,
U,Serena Williams,Virat Kohli,
G,Wayne Gretzky,Virat Kohli,
U,Virat Kohli,Virat Kohli,
U,Serena Williams,Virat Kohli,
G,Wayne Gretzky,Virat Kohli,

Here is my code:

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

typedef struct
{
    // members for the student's type, name, surname
    char type;
    char name[50];
    char surname[50];
} Student;

int main(void)
{
    // for comparing strcmp
    int result;

    // file pointer variable for accessing the file
    FILE *file;

    // attempt to open file.txt in read mode to read the file contents
    file = fopen("file.txt", "r");

    // if the file failed to open, exit with an error message and status
    if (file == NULL)
    {
        printf("Error opening file.\n");
        return 1;
    }

    // array of structs for storing the Student data from the file
    Student students[100];

    // read will be used to ensure each line/record is read correctly
    int read = 0;

    // records will keep track of the number of Student records read from the file
    int records = 0;

    // read all records from the file and store them into the students array
    while (fscanf(file, " %c , I[^,], I[^,],", &students[records].type, students[records].name, students[records].surname) == 3)
    {
        // if fscanf read 3 values from the file then we've successfully read
        records  ;
        // if there was an error reading from the file exit with an error message
        // and status
        if (ferror(file))
        {
            printf("Error reading file.\n");
            return 1;
        }
    }

    // close the file as we are done working with it
    fclose(file);

    // print out the number of records read
    printf("\n%d records read.\n\n", records);

    // print out each of the records that was read
    for (int i = 0; i < records; i  )
        printf("%c %s %s\n",
               students[i].type,
               students[i].name,
               students[i].surname);
    printf("\n");

    // change first record's name to Elena Heis
    for (int i = 0; i < 1; i  )
    {
        if (students[i].name == students[i].name)
        {
            printf("%s\n",
                   students[i].name);
            strcpy(students[i].name, "Elena Heis");
            printf("%s\n",
                   students[i].name);
        }
    }

    // write changes to file
    file = fopen("file.txt", "wb");
    if (file != NULL)
    {
        fwrite(students, sizeof(Student), 1, file);
        fclose(file);
    }

    return 0;
}

After write file has broken encoding like this enter image description here

It should be enter image description here

CodePudding user response:

Your code is clean and your format string is almost perfect, yet parsing the csv file (or any file in general) with fscanf() is not recommended as it is very difficult to recover from errors and newlines are mostly indistinguishable from other white space characters. In particular, the \n at the end of the format string will match any possibly empty sequence of white space characters.

Testing ferror() and feof() as you do seems fine but insufficient to ensure reliable parsing: if fscanf() returns a short code, for example because of an empty field, parsing will continue from the middle of the offending line and neither ferror() nor feof() will cause the loop to end.

You should instead read one line at a time with fgets() and use sscanf() to parse the line.

Also note these remarks:

  • the csv file does not seem to contain name and surname fields but rather the full names of opponents.

  • this file seems to have trailing , after the third field. If this is expected, the format string ensuring record validity should be changed to "%c,I[^,],I[^,\n]%1[,\n]"

  • you should check that records < 100 to avoid a buffer overflow.

  • the test if (students[i].name == students[i].name) is useless and always true. No test is needed to change the name of the first student.

  • you cannot write the text file with fwrite(students, sizeof(Student), 1, file), you should instead use fprintf as for the output to the terminal.

Here is a modified version:

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

typedef struct
{
    // members for the student's type, name, surname
    char type;
    char name[50];
    char surname[50];
} Student;

int main(void)
{
    // file pointer variable for accessing the file
    FILE *file;

    // attempt to open file.txt in read mode to read the file contents
    file = fopen("file.txt", "r");

    // if the file failed to open, exit with an error message and status
    if (file == NULL)
    {
        fprintf(stderr, "Error opening file %s for reading: %s\n", "file.txt", strerror(errno));
        return 1;
    }

    // array of structs for storing the Student data from the file
    Student students[100];

    // length of the students array
    int nrecords = sizeof(students) / sizeof(*students);

    // buffer to read one line at a time
    char buffer[256];

    // records will keep track of the number of Student records read from the file
    int records = 0;

    // read all records from the file and store them into the students array
    while (records < nrecords && fgets(buffer, sizeof buffer, file))
    {
        // Read a line/record from the file
        // if it was able to read successfully which we expect read will be 3
        char newline[3]; // for characters at end of line: `,\n` or `\n`
        char extra;      // to ensure no more characters are present
        // there are 5 conversion specifiers, but sscanf should parse
        // exactly 4 fields, including the trailing `,` and the newline
        int read = sscanf(buffer,
                          "%c,I[^,],I[^,\n]%2[,\n]%c",
                          &students[records].type,
                          students[records].name,
                          students[records].surname,
                          newline, &extra);

        // if fscanf read 3 values and a newline from the file then we've successfully read
        // in another record
        if (read == 4) {
            records  ;
        } else {
            fprintf(stderr, "invalid record: %s", buffer);
        }
    }
    // if there was an error reading from the file exit with an error message
    // and status
    if (ferror(file))
    {
        fprintf(stderr, "Error reading file: %s\n", strerror(errno));
        fclose(file);
        return 1;
    }

    // close the file as we are done working with it
    fclose(file);

    // print out the number of records read
    printf("\n%d records read.\n\n", records);

    // print out each of the records that was read
    for (int i = 0; i < records; i  )
    {
        printf("%c %s %s\n",
               students[i].type,
               students[i].name,
               students[i].surname);
    }
    printf("\n");

    // change first record's name to Elena Heis
    strcpy(students[0].name, "Elena Heis");
    printf("%s\n", students[0].name);

    // write changes to file
    file = fopen("file.txt", "w");
    if (file == NULL)
    {
        fprintf(stderr, "Error opening file %s for writing: %s\n", "file.txt", strerror(errno));
        return 1;
    }

    // print out each of the records that was read
    for (int i = 0; i < records; i  )
    {
        fprintf(file, "%c,%s,%s,\n",
                students[i].type,
                students[i].name,
                students[i].surname);
    }
    fclose(file);
    return 0;
}
  • Related