Scanning a CSV file with scanf() in a loop stops after the first line and beginning of second

Viewed 220

I need to read in a huge CSV file and store it in a struct, to be inserted into a binary search tree (but that's another story) however I can only get the first four columns to be read in (one line has four columns)- any ideas?

Intended output:

Michael,21,M,12345
Susan,34,F,13342
Robin,12,F,45423
Teddy,43,M,56231
Ed,21,M,34354
Ramirez,21,F,79563
Toby,32,M,34277
Guy,23,M,90765
Desiree,21,F,67453
Molly,24,F,45434
Alison,23,F,62356
Tara,26,F,45223
Sally,21,F,34545
Mike,20,M,67676
Roger,19,M,83737
David,21,M,78554
Davis,23,M,66554
Sheng,22,M,31211
Wesley,31,M,21436

etc. but I am getting

Michael,21,M,12345
Susan

Code:

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

#define MAXLEN 128

typedef struct{
    char name[MAXLEN];
    char age[MAXLEN];
    char sex[MAXLEN];
    char courseID[MAXLEN];
}input_t;

typedef struct{
    input_t input;
    struct bst *left;
    struct bst *right;
} bst;

void parse_csv(bst *node);
void print_csv(bst node);

void parse_csv(bst *node){
    while (scanf(" %[^,],%[^,],%[^,],%[^,]\n", node->input.name, node->input.age, node->input.sex, node->input.courseID) == 4) {
        break;
    }
}

void print_csv(bst node){
  printf("%s,%s,%s,%s\n",node.input.name, node.input.age, node.input.sex, node.input.courseID);
}

int main(int argc, char *argv[]) {
    bst node;
    parse_csv(&node);
    print_csv(node);
    return 0;
}

how the csv looks like, we will be given bigger test files to test on, though

1 Answers

This could read repeatedly (assuming the formatting is perfect and see below);
though always into the same node.

while (scanf(" %[^,],%[^,],%[^,],%[^,]\n", 
       node->input.name,  
       node->input.age, 
       node->input.sex, 
       node->input.courseID) == 4) {

This spoils the fun, by stopping unconditionally after the first time.

    break;
}

The "Susan", which seems to imply the start of reading a second row, is actually read as part of the first row, because of unsuitable format specifier, which allows reading anything non-",", including the newline and Susan.

In a comment you confirm that changing ,]\n -> ,\n] fixes the line parsing and gets you a correctly scanned first line.

If you also delete the break, scanning your CSV should work.
"Work" as in "read all the CSV, ignoring most of it and storing only the last line". In order to show more than one line you need of course

  • a data structure which can store more than one line (probably your BST)
  • fill that BST where currently your break is
  • change print_csv() to show the whole tree in some way

or

  • call print_csv() in a way to show the whole tree

Also see additional input by Jonathan Leffler in the comments on this answer.
They refer to what you describe as "another story".

As a side note (also mentioned by Jonathan Leffler):
This use case of a perfectly formatted and intended (probably generated) CSV can be handled by scanf() as one of the few exceptions. For anything with the slightest doubt on correct formatting, most prominently user input, the scanf() family is inappropriate, reading whole lines and doing error-tolerant parsing explicitly is the way to go there.
However, even in case of intended input being perfectly formatted, consider malicious input and protect yourself by restricting the length of scanned fields, in order to avoid writing beyond your arrays, e.g. beyond char name[MAXLEN] or char courseID[MAXLEN] depending on how your compiler arranges structs.

Related