Issues with sscanf in C - c

I am writing a program that could read a text with csv format and I am having this issue where the sscanf only parses the whole thing as one string when it is separated by ','.
For example, a code snippet below
char str[100] = "Alex,2933,89,";
char name[50] = "";
int id;
double mark;
sscanf(str, "%s,%d,%lf,", name, &id, &mark);
printf("%s\n", name);
printf("%d\n", id);
printf("%f\n", mark);
Output was:
Alex,2933,89,
896
0.000000
Which is clearly not the expected output.
But when str is edited to str = "Alex 2933 89 ", the code is giving me the correct output.
The working code:
char str[100] = "Alex 2933 89 ";
char name[50] = "";
int id;
double mark;
sscanf(str, "%s %d %lf ", name, &id, &mark);
printf("%s\n", name);
printf("%d\n", id);
printf("%f\n", mark);
Correct Output:
Alex
2933
89.000000
Can I know how do I fix this?

%s stops at white space, the fact that you put a , in the format string does not change this behavior. You should use the %[^,] conversion specification.
Change the code this way:
char str[100] = "Alex,2933,89,";
char name[50] = "";
int id;
double mark;
if (sscanf(str, "%49[^,],%d,%lf,", name, &id, &mark) == 3) {
printf("%s\n", name);
printf("%d\n", id);
printf("%f\n", mark);
}
Note however that %[^,] cannot parse empty fields. If the CSV line may contain empty fields, such as ,23,89.0, sscanf() will fail to convert name because no character matches the %[^,] specification. If you have such lines in the source file, you should parse the string fields manually with strchr() or strcspn().

Related

Values lossing in fscanf

The IDE I used is Clion.
I wanna read the Line-separated data stored in .txt file.
Each line contains firstname, surname, gender, ID and age, which are str, str, str, int and int.
StudentList.txt
Olivia SWANSON F 29001 20
Emma ONEILL F 7900 19
I try to use fscanf to read the data.
FILE *fp;
char fname[20];
char sname[20];
char gender[1];
int ID;
int age;
fp = fopen("C:\\Users\\Catlover\\Desktop\\DSA\\Program2\\StudentList.txt", "r");
while(fscanf(fp, "%s %s %s %d %d", fname, sname, gender, &ID, &age)!= EOF)
{
printf("%s,%s,%s,%d,%d\n", fname, sname, gender, ID, age);
}
fclose(fp);
return 0;
But the result it return looks like a little bit weird becasue it doesn't output the second value.
Result is
Olivia,,F,29001,20
Emma,,F,7900,19
Something shocks me is that the same code runned in PellesC lead to the correct result.
I used to learn C++ so there may exists some important rules in C but I didn't notice. Can anyone show that for me?
"%s" without width
Never use "%s" in a *scanf() without a width to indicate the max number of non-white-space characters to read and save. Recall that after reading, a null character is appended. Example: if the buffer size is 100, code can only read up to 99.
char gender[1]; is too small for "F".
Wrong check
fscanf(fp, "%s %s %s %d %d", ...) can return other values than 5 or EOF. As only 5 is acceptable, test against that.
Test open success
If fopen() fails, fscanf(), fclose() are bad
Other issues exist too*
But lets use start with fixing the above.
char fname[20 + 1];
char sname[20 + 1];
char gender[1 + 1];
int ID;
int age;
FILE *fp = fopen("C:\\Users\\Catlover\\Desktop\\DSA\\Program2\\StudentList.txt", "r");
if (fp) {
while(fscanf(fp, "%20s %20s %1s %d %d", fname, sname, gender, &ID, &age) == 5) {
printf("%s,%s,%s,%d,%d\n", fname, sname, gender, ID, age);
}
fclose(fp);
}
return 0;
You need to have space to accommodate null byte also.
char gender[1];
to
char gender[2];

Using fgets and sscanf for taking a string from a file

i have a file.txt structured this way:
author, "title", genre, price, copies_inStock
R. Tolkien, "The lords of the rings", Fantasy, 65.50, 31
i tried using fgets and sscanf
FILE *fp = NULL;
char string[100];
char title[30], author[30], genre[30];
float price;
int copies=0;
fp = fopen("text.txt", "r");
while(!feof(fp)) {
fgets(string, 100, fp);
sscanf(string, "%[^,],%[^,],%[^,],%f[^,],%d[^ ]", autore, titolo, genere, &prezzo, &copie);
}
fclose(fp);
printf("%s %s %s %.2f %d\n", author, title, genre, price, copies);
OUTPUT
R. Tolkien "The lord of the rings" fantasy 65,50 0
Why it don't access to the variable copies?
There are better ways? Thanks
The format specifiers on this line are incorrect
sscanf(string, "%[^,],%[^,],%[^,],%f[^,],%d[^ ]", autore, titolo, genere, &prezzo, &copie);
It should be
sscanf(string, "%[^,], %[^,], %[^,],%f,%d", autore, titolo, genere, &prezzo, &copie);
The additional spaces are to filter leading whitespace - it is not automatic with %[] (or with %c).
The %f and %d were a sort of mangled hybrid of what they should be. The conversion of those stops at the first character that cannot be used, without your intervention.
Side note: you really must check the result of scanf() function family: the number of successful conversions made.
"%f[^,]" is legal, yet certainly not what OP wants.
"%f" scans for a float, then "[^,]" scans for that 4 character sequence.
There are better ways?
Use " %n" to check scanning success. It records the offset of the scan.
Use width limits like 29 in %29[^,] to not overfill the char array.
Use a space before %29[^,] to consume optional leading whitespace.
Money is always tricky.
Do not use while(!feof(fp)). Check return from fgets().
char string[100];
char title[30], author[30], genre[30];
double price;
int copies;
FILE *fp = fopen("text.txt", "r");
if (fp) {
while(fgets(string, sizeof string, fp)) {
int n = 0;
sscanf(string, " %29[^,], %29[^,], %29[^,],%lf ,%d %n",
author, title, genre, &price, &copies, &n);
// If n==0, scan was incomplete
// If string[n], string has extra garbage
if (n == 0 || string[n]) {
fprintf(stderr, "Bad <%s>\n", string);
} else {
printf("%s %s %s %.2f %d\n", author, title, genre, price, copies);
// Robust code here would do additional work:
// Trim trailing string whitespace. Range checks on numeric values, etc.
}
}
fclose(fp);
}

How does sscanf separate a string into different variables?

Can someone explain how %25[^;] and %*[^ABCDF] are used in the following code?
char name[26], class[8];
int score;
char grade;
char record[50] = "Ross, Bob; MAC1234 85 A";
sscanf(record, "%25[ˆ;] %*c %6s %d %*[ˆABCDF] %c", name, class, &score, &grade);

How do I read using sscanf a character string with spaces and then a number?

I want to read from the keyboard data to be inserted in a list.
For example if I type ins "name_to_insert" 19930412, the character string name should be name_to_insert, without the quotation marks and date should be 19930412.
I don't know how to properly specify the second sscanf.
char op[4], name[30], s[50];
int date;
while (scanf("%[^\n]%*c", s)==1)
{
sscanf(s, "%s", op);
if (strcmp(op, "ins")==0)
{
sscanf(s, "%*s %[^0-9]%d", name, &date);
printf("Name is %s and date is %d\n", name, date);
}
}
I solved the problem with the quotes by simply removing the first and the last character from the name:
char op[4], nameTemp[30], *name, s[50];
int date;
while (scanf("%[^\n]%*c", s)==1)
{
sscanf(s, "%s", op);
if (strcmp(op, "ins")==0)
{
sscanf(s, "%*s %[^0-9]%d", nameTemp, &date);
name=nameTemp+1;
name[strlen(name)-2]='\0';
printf("Name is %s and date is %d\n", name, date);
}
}
You're almost right. Assuming the quotes are mandatory, you simply need to wrap the scan-set conversion specifier in quotes:
if (sscanf(s, "%*s \"%29[^\"]\" %d", name, &date) != 2)
...oops...
Note the %29[^0-9]; this limits the string to 29 bytes plus the terminal null '\0' that will fit in your 30-byte array. You should probably have a similar check on your outer scanf() too, or just use fgets() instead of scanf() there.
If the quotes are optional, you have to work a little harder, scanning for the name as non-digits and removing the quotes after the scan:
if (sscanf(s, "%*s %29[^0-9] %d", name, &date) != 2)
...oops...
Here's some test code:
#include <stdio.h>
static void scanner(const char *fmt, const char *s)
{
char name[30];
int date;
if (sscanf(s, fmt, name, &date) != 2)
printf("Scan failed {%s} and {%s}\n", fmt, s);
else
printf("{%s} and {%s} => {%s} %d\n", fmt, s, name, date);
}
int main(void)
{
char source[][30] =
{
"ins \"name in quotes\" 12345",
"ins name without quotes 12345",
};
enum { NUM_SOURCE = sizeof(source) / sizeof(source[0]) };
char format[][20] =
{
"%*s \"%29[^\"]\" %d",
"%*s %29[^0-9] %d",
};
enum { NUM_FORMAT = sizeof(format) / sizeof(format[0]) };
for (int i = 0; i < NUM_FORMAT; i++)
{
for (int j = 0; j < NUM_SOURCE; j++)
scanner(format[i], source[j]);
}
return 0;
}
Sample output:
{%*s "%29[^"]" %d} and {ins "name in quotes" 12345} => {name in quotes} 12345
Scan failed {%*s "%29[^"]" %d} and {ins name without quotes 12345}
{%*s %29[^0-9] %d} and {ins "name in quotes" 12345} => {"name in quotes" } 12345
{%*s %29[^0-9] %d} and {ins name without quotes 12345} => {name without quotes } 12345
The failed conversion is to be expected; the format looks for quotes and there aren't any.
I also played with a slightly different harness and the format string:
"%*s %1[\"]%29[^\"]%1[\"] %d"
(passing two 2-character strings, q1 and q2, to hold the quotes), but when the quotes were missing, the scan failed; the quotes aren't optional.
Note that the name has the trailing space and the quotes with the second format; those will have to be removed separately.
sscanf(s, "%*s \"%[^\"]\" %d", name, &date);

How to read specifically formatted data from a file?

I'm supposed to read inputs and arguments from a file similar to this format:
Add id:324 name:"john" name2:"doe" num1:2009 num2:5 num2:20
The problem is I'm not allowed to use fgets. I tried with fscanf but have no idea how to ignore the ":" and seperate the string ' name:"john" '.
If you know for sure the input file will be in a well-formed, very specific format, fscanf() is always an option and will do a lot of the work for you. Below I use sscanf() instead just to illustrate without having to create a file. You can change the call to use fscanf() for your file.
#define MAXSIZE 32
const char *line = "Add id:324 name:\"john\" name2:\"doe\" num1:2009 num2:5 num3:20";
char op[MAXSIZE], name[MAXSIZE], name2[MAXSIZE];
int id, num1, num2, num3;
int count =
sscanf(line,
"%s "
"id:%d "
"name:\"%[^\"]\" " /* use "name:%s" if you want the quotes */
"name2:\"%[^\"]\" "
"num1:%d "
"num2:%d "
"num3:%d ", /* typo? */
op, &id, name, name2, &num1, &num2, &num3);
if (count == 7)
printf("%s %d %s %s %d %d %d\n", op, id, name, name2, num1, num2, num3);
else
printf("error scanning line\n");
Outputs:
Add 324 john doe 2009 5 20
Otherwise, I would manually parse the input reading a character at a time or or throw it in a buffer if for whatever reason using fgets() wasn't allowed. It's always easier to have it buffered than not IMHO. Then you could use other functions like strtok() and whatnot to do the parse.
perhaps this is what you want ?
#include <stdio.h>
#include <string.h>
int main()
{
char str[200];
FILE *fp;
fp = fopen("test.txt", "r");
while(fscanf(fp, "%s", str) == 1)
{
char* where = strchr( str, ':');
if(where != NULL )
{
printf(" ':' found at postion %d in string %s\n", where-str+1, str);
}else
{
printf("COMMAND : %s\n", str);
}
}
fclose(fp);
return 0;
}
If output of it will be
COMMAND : Add
':' found at postion 3 in string id:324
':' found at postion 5 in string name:"john"
':' found at postion 6 in string name2:"doe"
':' found at postion 5 in string num1:2009
':' found at postion 5 in string num2:5
':' found at postion 5 in string num2:20

Resources