Reading Strings/words and integers from input file - c

I want to write a little program to read lines from a given .csv/.txt file and print out specific details based on user input.
I'm currently working with a
FILE *input = fopen("Example.csv", "r");
and the input looks like this:
Test000, 40, 0, empty
Test001, 0, -41, empty
Now if I try to to fscanf() from input, it only sets the first char[] and ignores the other variables.
My fscanf() call looks like this:
fscanf(input, "%s , %d , %d , %s", name, &timeA, &timeB, info);
# I'm calling fscanf(...) inside of while()-condition.
# while (fscanf(...) == 4) { *apply logic here* }
So, with this code, fscanf() only ever sets name to 'Test000,', then '40', '0', 'empty' etc., but ignores timeA, timeB, and info.
They are defined as:
char name[51];
int timeA = 0;
int timeB = 0;
char info[51];
I really don't know how to circumvent this problem. Any kind of help will be appreciated!
Thank you for your time.

A scanset could be used. %50[^,] will read up to 50 characters or to a comma.
fscanf(input, " %50[^,], %d , %d , %50s", name, &timeA, &timeB, info);
Note the space before &50[^,] to consume leading whitespace.
Check the return of fscanf. In this case 4 will be returned if all four items are successfully scanned.

fscanf() treats consecutive characters until it encounters white-space as part of a single string (char[]) - so the best option for you would be to remove the commas in your .txt file, and make your fscanf the following: fscanf(input, "%s %d %d %s", name, &timeA, &timeB, info); - your data should look like: Test000 40 0 empty. That's the most straightforward way of making it work.
If you want it to work with your current data format, fscanf() may not be the best option. You would be better off using some functions form <string.h>.
char data[512];
fgets(data, sizeof (data), input);
strcpy(name, strtok(data), ","));
timea = (int) strtol(strtok(data, ","), NULL, 10);
timea = (int) strtol(strtok(data, ","), NULL, 10);
strcpy(info, strtok(data, ","));
(strcpy and strtok are both avaible in <string.h>, strtol() is available in <stdlib.h>)
strcpy is used to copy "strings".
strtok splits a string (Note that it modifies the string it is passed!).
strtol converts a string to an long (which we cast to int).
There are more secure versions of some of the functions available (i.e. strtok_r() and strtol() also comes in an int version (so you don't need to cast its return value to int) called strtod()
If you are on a *nix system, it would be a good idea to run man function_name() (e.g. man strtok) to get a better idea of the function prototype and what it does/how it behaves etc. - or you can always read the man pages online, for example the FreeBSD Online Manual Pages where you can search for the function name and read the relevent man page.

Related

Write a program that takes a user's input and compares it to words in a file

I am in school and got an assignment to write a C program that takes an input from a user then scans a file and returns how many times that word shows up in a file. I feel like I got it 90% done, but for some reason I can't get the while loop. When I run the program it crashes at the while loop. Any help or guidance would be greatly appreciated.
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <windows.h>
int main() {
char input[50], file[50], word[50];
int wordcount;
printf("Enter a string to search for\n");
scanf("%s", input);
printf("Enter a file location to open\n");
scanf("%s", file);
FILE * fp;
fp = fopen("%s", "r", file);
while (fscanf(fp, "%s", word) != EOF) {
if (strcmp(word, input)) {
printf("found the word %s\n", input);
wordcount++;
}
}
printf("The world %s shows up %d times\n", input, wordcount);
system("pause");
}
You have 2 problems:
fp = fopen("%s", "r", file);
is incorrect, fopen expects only two arguments, not three. The correct version
is
fp = fopen(file, "r");
Note that there is no feature in the C language that allows you to construct
strings from variables like this "%s", variable1. This only works for function
like printf that read a format and interpret the format base on a fix set of
rules you can see here.
The second problem is this:
if (strcmp(word, input))
strcmp is used to compared two strings, however it return 0 when the strings
are equal, non-zero otherwise. So the correct check should be
if(strcmp(word, input) == 0)
{
printf("found the word %s\n", input);
wordcount++;
}
One last thing: when you read a string with scanf, you should limit the amount
of characters to be read, otherwise you will overflow the buffer and this yield
undefined behaviour which could lead to a segfault.
input is a char[50], so it can hold at most 49 characters, the better
scanf call would be
scanf("%49s", input);
with this you are making sure not to write beyond the bounds of the array.
Fotenotes
1The string "%s" has no real meaning in the C language, like any
other string it is merly a sequence of characters that ends with the
'\0'-terminating character. The memory layout for this strings is
+---+---+----+
| % | s | \0 |
+---+---+----+
The printf family of functions however give certains sequences of characters
(the ones beginning with %) a well defined meaning. They're used to determine the type of the variable that should
be used when printing as well as other format options. See the printf documentation for more information about that. You have to
remember however, that this type of constructs only works with printf because
printf was design to work this way.
If you need to construct a string using values of other variables, then you need
to have an array with enough space and use a function like sprintf. For
example:
const char *base = "records";
int series = 8;
char fn[100];
sprintf(fn, "%s%d.dat", base, series);
// now fn has the string "records8.dat"
FILE *fp = fopen(fn, "r");
...
But in your case this is unnecessary because the whole filename was already
stored in variable file, so construction a new string based on file is not
needed.
You are trying to open a file named "%s", which I'm pretty sure does not exist. If you had checked the return from fopen, you could have figured it out yourself.

Parsing file txt C

I guys i've this part of my code:
void token(){
FILE *pointer;
user record;
pointer = fopen("utente_da_file.txt","r+");
printf("OK");
fscanf(pointer , "%s, %s, %s, %s, %s \n" , record.nome_utente , record.nome , record.cognome , record.data_di_nascita , record.data_di_iscrizione);
fclose(pointer);
printf("TEXT -> %s \n" , record.nome_utente);
}
This is utente_da_file.txt
cocco,ananas,banana,ciao,miao
This is my output:
TEXT -> cocco,ananas,banana,ciao,miao
I don't understand why.
Greetings :)
This is due to the nature of %s parameter in scanf family: it consumes all characters up to the first white space character it encounters – or up to the end of input, whichever comes first (scanf - OK, C++ documentation, but applies for C alike). As you do not have any whitespace in your file, the entire content is consumed at once, including the commas, before you can scan for them in your format string...
You would get a hint for if you checked the return value of (f)scanf - it returns the number of variables filled, so you should have got 1 as return value.
Problem with (f)scanf family is that you cannot specify the delimiters for your strings to stop. So in your case, you will have to append white space in between the words of the file. But be aware that the comma will be part of the string then, if you append whitespace after them, you would have to append whitespace before so that your format string can consume them - this might make your file ugly, though, so you might prefer dropping it entirely then (but then drop them in the format string, too!).
Alternatively, you can read the entire line at once using fgets and then parse it using strtok. The whole procedure could look similar to the following piece of code:
char buffer[256];
fgets(buffer, sizeof(buffer), pointer);
char const* delimiters = ", \t\n\r";
char* token = strtok(buffer, delimiters);
if(token)
{
strncpy(record.nome_utente, token, sizeof(record.nome_utente));
if((token = strtok(NULL, delimiters)))
{
strncpy(record.nome, token, sizeof(record.nome));
// rest alike...
}
}
for me the best solution is to write thr C code in this way (a space between 2 %s):
fscanf(pointer , "%s %s %s %s %s \n" , record.nome_utente , record.nome , record.cognome , record.data_di_nascita , record.data_di_iscrizione);
and write your text file in this way (a space between two records):
cocco ananas banana ciao miao
In this way I'm sure it works well.
Ciao e buona fortuna.

Trying to read formatted file in C

i'm trying to read a formatted file in C this is a sample line of the file:
SURNAME;NAME;MINUTES'SECONDS''ONE-HUNDREDTHS OF A SECOND
I wrote this code:
while(!feof(fp))
{
fscanf(fp,"%[^;]s",surname);
fscanf(fp,"%c",&c);
fscanf(fp,"%[^;]s",name);
fscanf(fp,"%c",&c);
fscanf(fp,"%d'%d''%d",&min,&sec,&sec_cent);
fscanf(fp,"\n");
}
It works well with the name and surname strings, but it doesn't extract the time MINUTES'SECONDS''ONE-HUNDREDTHS OF A SECOND and i don't know why
Can someone help me?
There are a couple of things you may want to change in your code:
Always check the return value of scanf (or fscanf) to see if all the data were read.
Don't use feof() to control a loop.
You don't need to extract the '\n' with scanf, unless you are going to use getline() afterwards.
You don't need the s too, with that format specifier, but you should prevent buffer overflows limiting the number of chars read for each field.
You can use the modifier %*c to skip a char (the ;).
This should be fine:
int min = 0, sec = 0, sec_cent = 0;
char name[128], surname[128];
while ( fscanf(fp, "%127[^;]%*c%127[^;]%*c%2d'%2d''%2d", surname, name,
&min, &sec, &sec_cent) == 5 ) {
// do something...
}

Reading values from CSV file into variables

I am trying to write a simple piece of code to read values from a CSV file with a max of 100 entries into an array of structs.
Example of a line of the CSV file:
1,Mr,James,Quigley,Director,200000,0
I use the following code to read in the values, but when I print out the values they are incorrect
for(i = 0; i < 3; i++) /*just assuming number of entries here to demonstrate problem*/
{
fscanf(f, "%d,%s,%s,%s,%s,%d,%d", &inArray[i].ID, inArray[i].salutation, inArray[i].firstName, inArray[i].surName, inArray[i].position, &inArray[i].sal, &inArray[i].deleted);
}
Then when I print out the first name, the values are all assigned to the first name:
for(j = 0; j < 3; j++) /* test by printing values*/
{
printf("Employee name is %s\n", inArray[j].firstName);
}
Gives ames,Quigley,Director,200000,0 and so on in that way. I am sure it's how i format the fscanf line but I can't get it to work.
Here is my struct I'm reading into:
typedef struct Employee
{
int ID;
char salutation[4];
char firstName[21];
char surName[31];
char position[16];
int sal;
int deleted;
} Employee;
This is because a string %s can contain the comma, so it gets scanned into the first string. There's no "look-ahead" in the scanf() formatting specifier, the fact that the %s is followed by a comma in the format specification string means nothing.
Use character groups (search the manual for [).
const int got = fscanf(f, "%d,%[^,],%[^,],%[^,],%[^,],%d,%d", &inArray[i].ID,
inArray[i].salutation, inArray[i].firstName,
inArray[i].surName, inArray[i].position, &inArray[i].sal,
&inArray[i].deleted);
And learn to check the return value, since I/O calls can fail! Don't depend on the data being valid unless got is 7.
To make your program read the entire file (multiple records, i.e. lines), I would recommend loading entire lines into a (large) fixed-size buffer with fgets(), then using sscanf() on that buffer to parse out the column values. That is much easier and will ensure that you really do scan separate lines, calling fscanf() in a loop will not, since to fscanf() a linefeed is just whitespace.
Might as well post my comment as an answer:
%s reads a full word by default.
It finds the %d, the integer part, then the ,, and then it has to read a string. , is considered valid in a word (it is not a whitespace), so it reads until the end of the line (there is no whitespace until then), not until the first comma... And the rest remains empty. (From this answer)
You have to change the separator with specifying a regex:
fscanf(f, "%d,%[^,],%[^,],%[^,],%[^,],%d,%d", &inArray[i].ID, inArray[i].salutation, inArray[i].firstName, inArray[i].surName, inArray[i].position, &inArray[i].sal, &inArray[i].deleted);
Instead of %s, use %[^,], which means "grab all chars, and stop when found a ,".
EDIT
%[^,]s is bad, it would need a literal s after the end of the scanset... Thanks #MichaelPotter
(From Changing the scanf() delimiter and Reading values from CSV file into variables )

fgets and sscanf

This code is supposed to get integers from a file which is finput and sort it and gets the first integer in the file which is the number of integers to be sorted and the integers that follow are the integers to be sorted. I don't get how fgets and sscanf work together. Can someone explain how fgets and sscanf work in this code?
FILE *finput;
int *array_int, c1, no_elem;
char numlines[500];
fgets(numlines, 500, finput);
array_int = (int *)malloc(sizeof(int)*no_elem);
if ((sscanf(numlines, "%d", &no_elem) == 1) && array_int!= NULL)
{
for(c1=0; fgets(numlines, 500, finput) != NULL; )
{
if (sscanf(numlines, "%d", &array_int[c1])==1)
{
++c1;
}
}
}
fgets gets a string (i.e. a line of text) from the file.
sscanf parses a string based on the format string. It is reverse to sprintf. the <x>printf and matching <x>scanf functions allow formatted output and input accordingly, with a standard format string. For example, "%d" means "signed integer value", and in context of <x>scanf it means "read it into the next parameter in the following list of parameters" (your array member in your case).
You can parse directly from the file using fscanf, but using fgets + sscanf instead allows for more flexibility and might be safer.

Resources