I am doing a small project for college (1st semester doing a bookstore implementation) and I have a problem with reading from a text file into a list of structs, with a two-dimensional array of characters in it that stores authors. However, it doesn't work properly (every time I launch the program it shows list is empty). Writing to a file works (I think, because it overwrites my text file with empty data).
Example data:
Adam Mickiewicz///Pan Tadeusz/Publisher 1/1833/24.99
Jules Verne///Around The World in 80 days/Publisher 1/1904/19.99
Jean-Jacques Sempe/Rene Goscinny//Little Nicholas/Publisher 2/1963/22.99
My structure:
#define AK 3 // Constant denoting max number of authors
typedef struct
{
char authors[AK][100];
char title[255];
char publisher[100];
unsigned int year;
double price;
struct Book *next;
} Book;
Book *first; // Address of the first element in a list
Reading from file:
void read_from_file(const char *path)
{
int no_of_authors;
int i;
printf("Loading...\n");
FILE *fp = fopen(path, "r"); // Opening a file
// Checking for errors
if (!fp) {
printf("Error reading from file!");
return;
}
// The loading mechanism
no_of_authors = 0;
while (!feof(fp)) {
Book *new = (Book*) malloc(sizeof(Book));
for (i = 0; i < AK; i++) {
fscanf(fp, "%s/", new->authors[i]);
}
fscanf(fp, "%s/%s/%u/%lf", new->title, new->publisher,
&new->year, &new->price);
fscanf(fp, "\n");
new = new->next;
}
fclose(fp);
printf("Loading successful.");
}
Writing to file (just in case):
void write_to_file(const char *path, Book *first)
{
int i;
printf("Saving...\n");
FILE *fp = fopen(path, "w");
Book* current = first;
if (!fp) {
printf("Error opening the file!");
dump_list(first); // Dumping the list to prevent memory leaks, this works okay
}
// Saving mechanism
while (first != NULL) {
for (i = 0; i < AK; i++) {
fprintf(fp, "%s/", current->authors[i]);
}
fprintf(fp, "%s/%s/%u/%lf", current->title, current->publisher,
¤t->year, ¤t->price);
fprintf(fp, "\n");
}
fclose(fp);
printf("Saved successfully");
}
OP's biggest failing is not checking the return value of fscanf(). Had code done so, problems would be more rapidly detected.
When is comes to reading lines of data the first consideration is:
Could input be faulty?
With learner applications this is often considered no. Input is either "good" or end-of-file. Let us not assume data is too well formated.
As it turns out, while the data file may not be faulty, the code reading it may be wrong. The subtle 2nd reason for code to check the *scanf() return values - self validation.
For line orientated data, it is much better to read is a line of data with fgets() than feof(), fscanf()... See also #Paul Ogilvie
char buf[sizeof(Book) * 2]; // Use an ample sized buffer
while (fgets(buf, sizeof buf, fp)) {
Use "%s" to read in text that does not include white-space. This will also read in '/'. Since '/' is use to delimit, "%s" is not an acceptable input specifier. Further names like "Adam Mickiewicz" include a space. A 2nd reason to not use "%s".
Consider what fscanf(fp, "%s/", new->authors[i]); is doing. "%s" scans into new->authors[i] non-white-space characters. A character after non-white-space characters is a white-space, never a '/'.
Use "%[^/]" to read in text that does not include '/'.
Use "%n" to keep track of the current scan offset.
Now parse the line.
char *p = buf;
Book *nu = malloc(sizeof *nu); // no cast needed
for (size_t i = 0; i < AK; i++) {
int n = 0;
sscanf(p, "%99[^/]/%n", nu->authors[i], &n);
if (n == 0) {
Handle_Parse_Error();
}
p += n;
}
if (sscanf(p, "%254[^/]/%99[^/]/%u/%lf",
nu->title, nu->publisher, &nu->year, &nu->price) != 4) {
Handle_Parse_Error();
}
Robust code would add checks on each member. Suit to coding goals.
if (nu->year < -6000 || nu->year > 2999) Fail_Year_Range();
Further work is needed to link data together. OP's code is unclear on this matter and so left to OP. A possible approach:
Book *read_from_file(const char *path) {
Book first; // Only the .next member is used.
first.next = NULL;
Book *current = &first;
// setup while () loop
...
while (fgets(buf, sizeof bu, fp)) {
...
Book *nu = malloc(sizeof *nu);
nu->next = NULL;
...
// parse data, fill in rest of nu->
....
current->next = nu; // update current pointer
current = nu;
}
// close up file ...
return first.next;
}
I'm having a problem with some code in my program. I need to read a file and put it's content line by line into a struct. The file is about 800 lines long, and when i try to print my struct, which should now contain the content of the file, it only print about 30 of the lines as they should be. The rest is filed with error or wrong formatting. This is my function as it is now, and i simply call it in main. I am not sure what is wrong but maybe it has something to do with my malloc call?
void read_file(void){
int lines = count_lines(); /*function to count amount of lines in file*/
FILE *file;
int i = 0;
char filename[] = "race.txt";
file = fopen(filename, "r");
race_info *race = malloc(sizeof(race_info));
if (file != NULL) {
while (i < lines) {
fscanf(file, " %[A-Za-z]s %[A-Za-z]s %[A-Z]s %d %[A-Z]s %[A-Z]s %d %d",
race[i].race_name,
race[i].name,
race[i].lastname,
&race[i].age,
race[i].team,
race[i].country,
&race[i].position,
&race[i].time);
i++;
}
}
else {
perror(filename); //print the error message
}
for (i = 0; i < lines; i++) {
printf("%s %s %s %d %s %s %d %d",
race[i].race_name,
race[i].name,
race[i].lastname,
race[i].age,
race[i].team,
race[i].country,
race[i].position,
race[i].time);
}
fclose(file);
}
The struct is setup as following:
#define MAX_CHAR 100
struct race_info{
char race_name[MAX_CHAR];
char name[MAX_CHAR];
char lastname[MAX_CHAR];
int age;
char team[MAX_CHAR];
char country[MAX_CHAR];
int position;
int time;
};
typedef struct race_info race_info;
The lines from the file is setup as:
RaceName "Name LASTNAME" AGE TEAM Country Position TIME
The goal is to print the struct so that all 800 lines are printed with the same formatting as the file. But when printed it does only prints about 200 lines and and it does not go from start of file to the end, but takes content from the middle of it. A lot of the lines also have wrong formatting.
This race_info *race = malloc(sizeof(race_info)); seems to only allocate space for a single race_info.
You should probably have malloc(lines * sizeof(race_info)) for all the lines to fit.
So I want to save the best 3 scores of the game and to put them on a file. But for some reason when I read the file the best scores are 53,32,32. Not using name for now, just the 3 scores. And also I'm not familiarized with files.
typedef struct score{
unsigned char score[3];
//char name[20];
} SCORES;
This is how I'm saving.
void guardar_highscore (SCORES top){
FILE *f;
f = fopen ("/var/www/html/highscore3.txt","wb");
if (f ==NULL)
perror ("nope2"),exit (1);
fprintf(f,"%d \n %d \n %d \n",top.score[0],top.score[1],top.score[2]);
fclose(f);
}
This is how I'm reading it to the struct.
SCORES ler_highscore (){
SCORES top={0};
int i=0;
char line[20];
FILE *f;
f = fopen ("/var/www/html/highscore3.txt","rb");
if (f ==NULL)
perror ("nope"), exit (1);
while(fgets(line,20, f) != NULL){
sscanf (line, "%c", &top.score[i]);
i++;
}
fclose(f);
return top;
}
typedef struct score{
unsigned char score[3];
//char name[20];
} SCORES;
Scores are generally numbers, so it doesn't make much sense to store them as a single character. The problem becomes clear when you notice that you're writing them as integers (and with an extra space)...
fprintf(f,"%d \n %d \n %d \n",top.score[0],top.score[1],top.score[2]);
But you're reading them as characters...
sscanf (line, "%c", &top.score[i]);
53, 32, 32 looks suspiciously like the ASCII numbers for 5 and two spaces. If you write the character 5 as a number you'll get 53. That's because the character 5 is the number 53. Have a look at the ASCII table to see why.
The solution is to use integers consistently.
typedef struct {
int score[3];
} Scores;
Note that ALL_CAPS is generally reserved for constants, not types.
guardar_highscore remains basically the same, though I've cleaned it up some.
// The filename is now a variable so its used consistently
// and can be used in error messages.
const char Score_File[] = "highscore3.txt";
void guardar_highscore(const Scores *top) {
FILE *fd = fopen (Score_File,"wb");
if (fd == NULL) {
// A more informative error message than "nope".
fprintf( stderr, "Could not open '%s' for writing: %s\n", Score_File, strerror(errno) );
exit(1);
}
// Loop instead of repeating the formatting. This makes adding more
// scores easier.
// Note the stray whitespace is gone.
for( int i = 0; i < 3; i++ ) {
fprintf(fd, "%d\n", top->score[i]);
}
fclose(fd);
}
ler_highscore() changes to read as integers. It also only reads three lines to protect against overflowing the 3 element list if the file is unexpectedly large. It's good practice to never trust your input.
Scores ler_highscore() {
// This is the proper way to initialize a struct,
// each field must be initialized separately.
// A bare {0} happens to work because the struct is
// currently just a list, and if it doesn't you're
// going to overwrite all the elements anyway.
Scores top = { .score = {0} };
// No reason to skimp on the size of the line buffer.
char line[1024];
FILE *fd = fopen(Score_File, "rb");
if (fd == NULL) {
// Again, more informative error message.
fprintf( stderr, "Could not open '%s' for reading: %s", Score_File, strerror(errno) );
exit (1);
}
// Read 3 lines, no more. Otherwise we'll overflow memory.
for( int i = 0; i < 3; i++ ) {
// Use `sizeof(line)` rather than repeating the number.
// It avoids mistakenly letting them go out of sync.
if( fgets(line, sizeof(line), fd) == NULL ) {
fprintf( stderr, "Not enough scores in %s\n", Score_File );
break;
}
// Read one integer per line.
sscanf(line, "%d", &top.score[i]);
}
fclose(fd);
return top;
}
I have a project of a phone book, I have a function to read the structure from a file, put it into an array of structure. So to make sure that it reads correctly I print it into an output file but the result of the output file,
0 (null) 6553280
I have a CSV file with the data like
Ahmed,Mohamed,26 Elhoreya Street,15,Alexandria,4876321,ahmed#gmail.com
Sarah,Zaki,7 Smouha,36,Alexandria,3974542,sarah#hotmail.com
The output is null, it doesn't (read/write) correctly, while using the debugger it shows that it's reading. Why?
int i;
int counter;
struct pb //main struct
{
char Firstname[25];
char Lastname[25];
char street[20];
int street_no ;
char city[15];
int number;
char email[50];
};
struct pb k[1000];
void read_str(struct queue *queue)
{
{
counter = 0 ;
FILE *read ;
char filename[40];
printf("Enter file name \n");
scanf("%s",&filename);
read=fopen(filename,"r");
if (read == NULL)
printf("Error");
else
while(!feof(read))
{
struct pb *n= malloc(sizeof(struct pb));
fscanf(read,"%[^,],%[^,],%[^,],%d,%[^,],%d,%s\n",
k[counter].Firstname, k[counter].Lastname,
k[counter].street, &k[counter].street_no,
k[counter].city, &k[counter].number, k[counter].email );
counter++;
}
fclose(read);
}
}
int main()
{
read_str(&k);
FILE *read ;
read=fopen("out.txt","w");
fprintf(read,"%s %s %s %d %s %d %s ",
k[counter].Firstname, k[counter].Lastname,
k[counter].street, k[counter].street_no, k[counter].city,
k[counter].number, k[counter].email );
fclose(read);
return 0 ;
}
I can at least at a first glance see that the value of the counter during fprintf in main function is one past the end of your valid structure array (because of counter++ after the fscanf), which means it is undefined.
Moreover, I think you want to run a loop to fprintf all the records (structs). But you didn't.
Your orderings of fscanf and fprintf format specifiers are inconsistent.
It is clear that your code is doing nothing useful in the main function.
Update
Minimally corrected code:
#include <stdio.h>
int counter;
struct pb //main struct
{
char Firstname[25];
char Lastname[25];
char street[20];
int street_no ;
char city[15];
int number;
char email[50];
};
struct pb k[1000];
void read_str()
{
FILE *fin;
char filename[40];
counter = 0 ;
printf("Enter file name \n");
scanf("%s",filename);
if((fin=fopen(filename,"r"))!=NULL)
{
while(!feof(fin))
{
fscanf(fin,"%[^,],%[^,],%[^,],%d,%[^,],%d,%s\n",k[counter].Firstname, k[counter].Lastname, k[counter].street, &k[counter].street_no, k[counter].city, &k[counter].number, k[counter].email);
++counter;
}
fclose(fin);
}
}
int main()
{
int i;
FILE *fout;
read_str();
if((fout=fopen("out.txt","w"))!=NULL)
{
for(i=0; i<counter; ++i)
{
fprintf(fout,"%s %s %d %s %s %s %d\n",
k[i].Firstname, k[i].Lastname, k[i].street_no,
k[i].street,k[i].city,k[i].email,k[i].number );
}
fclose(fout);
}
return 0 ;
}
N.B. There are still many caveats in this code.
In addition to not writing beyond the end of your array-of-structs when reading your data, there are several additional areas where you may want to revise the approach you have taken with your code.
First, unless there is a compelling reason to declare your data structure as a global variable, you should limit its scope to main() and pass the array-of-structs as a parameter to any functions that need access to the data. In addition, when dealing with constants in your program (e.g. max phonebook entries 1000), it is good practice to either define a constant (#define MAXE 1000) or preferably use an enum to define the constant, e.g.:
enum { MAXE = 1000 };
(An anonymous enum is fine.)
You can also simplify your life by creating a typedef to your struct as well which will make passing the array-of-structs as a parameter easier. For instance you can declare a typedef to your struct (either named or anonymous) as follows:
typedef struct {
char Firstname[25];
char Lastname[25];
char street[20];
int street_no ;
char city[15];
int number;
char email[50];
} pb;
This will allow a simple declaration in main(), e.g.:
pb k[MAXE] = {{{0},{0},{0},0,{0},0,{0}}};
While not required, it is also good practice to initialize all variables, (including your array-of-structs), when they are declared.
While in this instance, there is little difference between reading the data file with fscanf or using a line-oriented input function, you will generally find reading a line at a time with fgets or getline and then parsing the line into components with sscanf or simple pointers will provide a more flexible and robust input routine. Regardless whether you read with fscanf or read with fgets and parse with sscanf always check the returns of fscanf or sscanf to validate the number of successful conversions.
The benefit of using line-oriented input functions to read each line of text from your input file is it will eliminate the rigidity of the fscanf format-string from the actual read of the file and allow you to handle separating the values after the line has been successfully read into a buffer. An example of using fgets in your case could be:
/* read addresses from input file up to a maximum of MAXE
* addresses. updates 'idx' pointer to hold the number of
* addreses read from file and returns number read
*/
size_t read_str (pb (*k)[], size_t *idx, FILE *fp)
{
char tmp[MAXL] = {0};
while (*idx < MAXE && fgets (tmp, MAXL, fp)) {
// printf ("read[%zu]\n", *idx);
if (sscanf (tmp, " %24[^,],%24[^,],%19[^,],%d,%14[^,],%d,%49[^\n]",
(*k)[*idx].Firstname, (*k)[*idx].Lastname,
(*k)[*idx].street, &(*k)[*idx].street_no,
(*k)[*idx].city, &(*k)[*idx].number, (*k)[*idx].email) != 7) {
fprintf (stderr, "read_str() error: parse of line[%zu] failed.\n",
*idx);
break;
}
(*idx)++;
}
return *idx;
}
note also the return of the number of address entries read allowing you to gauge success/failure of the function as well as providing you with the number of entries read. The number of entries read (idx) is also passed as a pointer to the function making the number of entries available in the calling function (main() here) regardless of whether the return is assigned.
Beyond those initial issues, you will want to validate each action you take that has consequence for the continued operation of your code. (e.g. all file opens, reads, writes, etc...) Putting those pieces together and adding basic validation, and using line oriented input, another approach to your task could look like the following:
#include <stdio.h>
/* constants for max input line and max entries */
enum { MAXL = 256, MAXE = 1000 };
typedef struct {
char Firstname[25];
char Lastname[25];
char street[20];
int street_no ;
char city[15];
int number;
char email[50];
} pb;
size_t read_str (pb (*k)[], size_t *idx, FILE *fp);
void print_str_fmt (pb *k, size_t idx);
int print_str (pb *k, size_t idx, FILE *fp);
int main (int argc, char **argv) {
if (argc < 3) { /* validate input/output filenames given as arguments */
fprintf (stderr, "error: insufficient input, usage: %s infile outfile\n",
argv[0]);
return 1;
}
pb k[MAXE] = {{{0},{0},{0},0,{0},0,{0}}}; /* initialize variables */
size_t index = 0;
FILE *ifp, *ofp;
if (!(ifp = fopen (argv[1], "r"))) { /* validate input file open */
fprintf (stderr, "error: file open failed '%s'\n", argv[1]);
return 1;
}
if (!(ofp = fopen (argv[2], "w"))) { /* validate output file open */
fprintf (stderr, "error: file open failed '%s'\n", argv[2]);
return 1;
}
if (!read_str (&k, &index, ifp)) { /* validate entries read */
fprintf (stderr, "error: read_str - no addresses read\n");
return 1;
}
fclose (ifp); /* close input file */
printf ("The addresses are:\n\n");
print_str_fmt (k, index);
if (print_str (k, index, ofp)) { /* validate entries written */
fprintf (stderr, "error: print_str - no addresses read\n");
return 1;
}
fclose (ofp); /* close output file */
return 0;
}
/* read addresses from input file up to a maximum of MAXE
* addresses. updates 'idx' pointer to hold the number of
* addreses read from file and returns number read
*/
size_t read_str (pb (*k)[], size_t *idx, FILE *fp)
{
char tmp[MAXL] = {0};
while (*idx < MAXE && fgets (tmp, MAXL, fp)) {
// printf ("read[%zu]\n", *idx);
if (sscanf (tmp, " %24[^,],%24[^,],%19[^,],%d,%14[^,],%d,%49[^\n]",
(*k)[*idx].Firstname, (*k)[*idx].Lastname,
(*k)[*idx].street, &(*k)[*idx].street_no,
(*k)[*idx].city, &(*k)[*idx].number, (*k)[*idx].email) != 7) {
fprintf (stderr, "read_str() error: parse of line[%zu] failed.\n",
*idx);
break;
}
(*idx)++;
}
return *idx;
}
/* formatted print of addressbook to stdout */
void print_str_fmt (pb *k, size_t idx)
{
size_t i;
for (i = 0; i < idx; i++)
printf (" %s %s\n %s No. %d\n %s, %d\n %s\n\n",
k[i].Firstname, k[i].Lastname, k[i].street, k[i].street_no,
k[i].city, k[i].number, k[i].email);
}
int print_str (pb *k, size_t idx, FILE *fp)
{
size_t i;
for (i = 0; i < idx; i++)
if (fprintf (fp, "%s,%s,%s,%d,%s,%d,%s\n",
k[i].Firstname, k[i].Lastname, k[i].street, k[i].street_no,
k[i].city, k[i].number, k[i].email) < 0)
return 1;
return 0;
}
Compile
gcc -Wall -Wextra -O3 -o bin/readstructsscanf readstructsscanf.c
Test Input
$ cat ../dat/phonebook.txt
Ahmed,Mohamed,26 Elhoreya Street,15,Alexandria,4876321,ahmed#gmail.com
Sarah,Zaki,7 Smouha,36,Alexandria,3974542,sarah#hotmail.com
Use/Output
$ ./bin/readstructsscanf ../dat/phonebook.txt foo.txt
The addresses are:
Ahmed, Mohamed
26 Elhoreya Street No. 15
Alexandria, 4876321
ahmed#gmail.com
Sarah, Zaki
7 Smouha No. 36
Alexandria, 3974542
sarah#hotmail.com
Confirm Output File
$ diff ../dat/phonebook.txt foo.txt
$
As with all problems in C, there are usually many ways to approach a correct solution. Hopefully this will give you a few additional ideas on how to make your code more flexible and robust.
This is for a hotel reservation system, that take a .txt file that contains lines of int string string int which it then reads and put into an array of type room... while scanning it keeps giving me segmentation fault... this is for class and i dont want a ready code to leech of but i just dont get why i keep getting segmentation... :/
#include <stdio.h>
#include <stdlib.h>
typedef struct{
int num;
char first[100];
char last[100];
int type;
}room;
int main (int argc, char ** argv){
FILE * myFile;
if(argc !=2)
{
fprintf(stderr, "Usage: %s <filename>\n", argv[0]);
return EXIT_FAILURE;
}
myFile = fopen(argv[1],"r");
if (myFile==NULL){
fprintf(stderr, "no open!!\n");
return EXIT_FAILURE;
}
// counter to add elements to my array
int i = 0;
char c;
//array of room with the 150 rooms in it...
room * rooms = malloc(150 * sizeof(room));
while ((c = getc(myFile)) != EOF ){
fscanf(myFile, "%d", rooms[i].num);
printf("the room num is: %d", rooms[i].num);
fscanf(myFile, "%s", rooms[i].first);
fscanf(myFile, "%s", rooms[i].last);
fscanf(myFile, "%d", rooms[i].type);
i++;
}
fclose(myFile);
}
Here is what i fixed in my code and worked but it is literally skipping the fist integer in the .txt file that it reads from... it just reads a zero when it should be a 1 so i noticed that the "(c = getc(myFile)) != EOF" was my problem, it is skipping the first integer it is supposed to read :/
while ((c = getc(myFile)) != EOF ){
fscanf(myFile, "%d", &rooms[i].num);
fscanf(myFile, "%s", rooms[i].first);
fscanf(myFile, "%s", rooms[i].last);
fscanf(myFile, "%d", &rooms[i].type);
printf("the room num is: %d and is occupied by %s %s and it is a %d\n", rooms[i].num, rooms[i].first, rooms[i].last, rooms[i].type);
i++;
}
The .txt file's first line is as follows:
1 carri alston 0
In your code
fscanf(myFile, "%d", rooms[i].num);
should be
fscanf(myFile, "%d", &rooms[i].num);
same with the type thing.
Along with that, you should always check the return value of fscanf() to ensure proper scanning.
also, you need to put a check on the value of i so that it should not access out of bound memory.