0

I have a csv file in which I have to separate every line (\n) and every field (,) in this line. My goal is to create an array of structs. Every struct in each "box" of the array must contains 4 fields of every line. How can I write it in c? I thought to use fscanf and fgets but I don't know how to use them together because with fgets I want to divide lines while with fscanf i want to divide fields .

Final situation :

| 0 , noto, 233460, 32209.073312 | 1, piangea, 4741192, 811.. | 2 ,spenti! , .... |

| position 0 in the array | position 1 in the array | position 2 in the array |

records.csv

0,noto,233460,32209.073312
1,piangea,4741192,81176.622633
2,spenti!,1014671, 4476.013614
3,misericordia,496325,61628.929334
4,quando,4476757,10838.641053

main.c

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

struct _SortedArray {
    int id;
    char field1[12];
    int field2;
    float field3;
};

int main() {
    FILE *fd;
    int res;

    struct _SortedArray files[101];
    int n;


    fd = fopen("records.csv", "r");
    if (fd == NULL) {
        perror("Error");
        exit(1);
    }

    char r[100];
    n = 0;

    while (n<6) {

        if(fgets(r,100,fd)!=NULL){
            puts(r);
            fscanf(r, "%[^,],%[^,],%[^,],%[^\n]\n", &files[n].id, &files[n].field1, &files[n].field2, &files[n].field3);
        }

        n++;
    }

    for(int i=0;i<6;i++){
        printf(" INT:%c,CHAR:%s //",files[i].id, files[i].field1);
    }
    return 0;
}
Serge Ballesta
  • 143,923
  • 11
  • 122
  • 252
Megan
  • 43
  • 5
  • `fscanf` directly reads from the input stream, so you can use only this instead of `fgets`, otherwise if you want to scan a string you already read, `sscanf` is what you want. – joH1 Nov 06 '20 at 10:36
  • If I understood correctly your code, you read a line with `fgets`, print it with `puts` and try to read from the same line with `fscanf`, right? Because it won't work as you expect... `puts` outputs to a different stream from the one where `fgets` and `fscanf` read, so you will "eat" the first line and `fscanf` will read only the next line! – joH1 Nov 06 '20 at 10:40
  • Thank you! I tried to use fscanf only , but I can't find the way to separate lines with \n. It doesn't work. @joH1 – Megan Nov 06 '20 at 10:51
  • No problem! Actually you are right about using fgets: [fscanf itself does not read lines](https://stackoverflow.com/a/18098682/6337519) – joH1 Nov 06 '20 at 11:01
  • I don't understand how to separate line 0 from line 1 with sscanf , which is the sintax? I wrote `sscanf(r,"%[^],%[^\n]\n", &files[n].id, &files[n].field1);` but it doesn't work... @joH1 – Megan Nov 06 '20 at 11:19

1 Answers1

1

Your code contains various little problems and a major inconsistency. The major inconsistency is that you should use sscanf instead of fscanf to process the line returned by fgets.

But that is not all:

  • misericordia has 12 characters. As C strings require a NULL terminator, field1 must have at least 13 as size
  • the format characters should be consistent with the type of the fields
  • when you read into a char array, the array decays to a pointer: you must not add th &

So the line could become:

sscanf(r, "%d,%[^,],%d,%f", &files[n].id, files[n].field1, &files[n].field2, &files[n].field3)

Other possible improvements:

  • identifiers starting with _ should be reserved for those that you do not use. Close to an opinion, but here you should better use SortedArray
  • replace plain magic values for sizes with the sizeof operator where you can. If you later change a size, you will have to change it in one single place in your code (best practice: Don't Repeat Yourself)
  • control the result of input functions (here [s]scanf to be robust against erroneous input data
  • eventually control that nothing is left at the end of line
  • only try to print as many lines as you could read
  • remove unused variables (a nice compiler should emit warnings)
  • always limit input of string to the size of the buffer (%12[^,])

The code could become:

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

struct SortedArray {
    int id;
    char field1[13];
    int field2;
    float field3;
};

int main() {
    FILE *fd;
    // int res;

    struct SortedArray files[101];
    int n;


    fd = fopen("records.csv", "r");
    if (fd == NULL) {
        perror("Error");
        exit(1);
    }

    char r[100];
    for (n=0; n<sizeof(files)/sizeof(files[0]); n++) {

        if(fgets(r,sizeof(r),fd)==NULL){
            break;
        }
            char dummy[2];    // to control nothing is left on end of line
           //puts(r);
           if (4 != sscanf(r, "%d,%12[^,],%d,%f%1s", &files[n].id, files[n].field1, &files[n].field2, &files[n].field3, dummy)) {
               perror("Incorrect line");
               fprintf(stderr, "Line %d : %s\n", n+1, r);
        }
    }

    for(int i=0;i<n;i++){
        printf(" INT:%d,CHAR:%s //",files[i].id, files[i].field1);
    }
    return 0;
}
Serge Ballesta
  • 143,923
  • 11
  • 122
  • 252
  • Thank you, can I ask you another question please? It works , but when I print Field3 (Float), it gives me wrong number after the point. For example : in the first line the number is 32209.073312 and it returns 32209.074219 why? – Megan Nov 06 '20 at 16:02
  • Because [floating point is broken](https://stackoverflow.com/q/588004/3545273)... More seriously single precision `float` have only a 24 bit mantissa, meaning a precision of not more than 7 decimal digits. Use `double` to limit the loss in precision. – Serge Ballesta Nov 06 '20 at 16:52