Home > Back-end >  how to read multiple variables separated by commas using only C fscanf()
how to read multiple variables separated by commas using only C fscanf()

Time:01-31

Say Given a text file that looks like this:

a,b,c
x,y,z

where a is a char *, b contains a float and c contains a double.
For an example, the input file can look like this:

apple,$12.34,test130.8
x,y,z

I want to use fscanf() to read a, b, c and assign each one of them to a corresponding variable. "apple" will be assigned to A of the same data type; "12.34"(not "$12.34") will be assigned to B with a float data type; so on.

My attempt was as follows:

fp = the file pointer

char A[50];
float B;
double C;

fscanf(fp, "%[^,],%[^,],%[^,]\n", A, B, C);

But I realized that %[^,]can only specify type char *; ergo, I'm not allowed to assign type char * to a float or double variable. Is there a way to parse %[^,] to make it only specifies type float?

if I only use this:

fscanf(fp, "%s,%f,%lf\n", A, B, C);

It will be thrown off by the "$" in "12.34", and it will give me 0.000000.

CodePudding user response:

Using sscanf() (instead of fscanf()) for ease of testing:

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

int main() {
    char *s = "apple,$12.34,test130.8\npear,$23.45,abc";
    for(int offset = 0, n;; offset  = n) {
        char *symbol;
        float price;
        char *note;
        if(sscanf(s   offset, " %m[^,],$%f,%m[^\n]%n", &symbol, &price, &note, &n) != 3) {           
            break;
        }
        printf("symbol: %s, price: %f, note: %s\n", symbol, price, note);
        free(note);
        free(symbol);
    }
}

and the matching output (note how it demonstrate the evils of using floating points for money):

symbol: apple, price: 12.340000, note: test130.8
symbol: pear, price: 23.450001, note: abc

I used %m to have scanf() allocate the strings. If I knew the maximum size of the strings I would reuse a fixed size strings instead of dynamically allocating and freeing those.

When using fscanf() instead of break you could use feof() to see if we are done, or if the input is invalid. If it's invalid you may want to resync to the next \n with fsccnf(..., "%c", ch). For the above s[offset] == '\0' will tell if you are the end but see below.

You may find it's much easier to get a line with fgets(), then use sscanf() similar to above to extract each item. If fails you can report the line and just read the next one. fgets() will return NULL if you have no more data and it leads to cleaner code when you separate I/O and parsing.

CodePudding user response:

There's already an answer from @AllanWind (using dynamic allocation for strings that my old library doesn't do.) Here's an alternative solution (that is much the same.)

First, the input file used for testing:

apple,$12.34,test130.8
banana,$20.67,testing201.45

Then the code using fscanf() with a complicated format string:

int main( void ) {
    FILE *fp = fopen( "test.txt", "r" );
    if( fp == NULL) {
        fprintf( stderr, "fopen() failed\n" );
        return -1;
    }

    char txt[50], word[12];
    double dval1, dval2;

    while( fscanf( fp, " I[^,],%*c%lf,[^0123456789]%lf", txt, &dval1, word, &dval2 ) == 4 )
        printf( "'%s' / %.2lf / '%s' / %.2lf\n", txt, dval1, word, dval2 );

    fclose( fp );

    return 0;
}

Finally, the output

'apple' / 12.34 / 'test' / 130.80
'banana' / 20.67 / 'testing' / 201.45
  • Related