How to retrieve data from a text file?

Pages: 123
Y'll need to take some cold showers.....

Please, never, never, ever use fgets() or fscanf("%s") in any code. This causes so many problems. It should be a compiler reported error!

Use of fscanf("%ns") is slightly better as at least the maximum number of chars to be read (excluding the null terminator) can be specified. However, if there are more chars than could have been read if not for the length specifier then these are NOT read - but remain. The next time a fscanf(%ns") is used then these characters are first read, and not the start of the next input field -which is probably what is expected/wanted. What is needed is to extract and ignore these 'extra' field characters.

Consider:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
#include <stdio.h>
#include <ctype.h>

bool fgetstr(FILE* f, char* buff, int sz)
{
	char form[10];
	int ch;

	snprintf(form, sizeof(form), "%%%is", sz - 1);
	fscanf(f, form, buff);
	while (((ch = fgetc(f)) != EOF) && !isspace(ch));

	return ch != EOF;
}

int main()
{
#define FBUFSZ 5

	FILE* f = fopen("text.txt", "r");

	if (f == NULL) {
		puts("Cannot open file text.txt");
		return 1;
	}

	char fbuff[FBUFSZ];

	while (fgetstr(f, fbuff, FBUFSZ))
		puts(fbuff);
}


Given the contents of the file as:
1
2
3
4
5
abc defg
hijklmn op
qrs tuvwx
yx12345
678987654


Then the read strings are:

1
2
3
4
5
6
7
8
abc
defg
hijk
op
qrs
tuvw
yx12
6789


Only using the allowed first chars from each field and ignoring the rest of that field.

Also, when using a length specifier for the number of chars in an array, this number should be defined once at the start and that name used whenever. If you just specify a number then if that size of the char array is changed, then everywhere that number is used has to be changed - with the chance that not every occurrence is found and changed. If a number is defined then just changing that one definition will have all occurrences changed - as per the sample code above.

The point with this exercise all along for me was the very telling char[25] for all data members.

While buffering has always been the answer (I agree a global const int LIMIT = 25 is all that's required for subsequent handling as one way) as is normal with ANSI C practice, the whole issue detracts from @OP's substantive problems.

We could have equally obscured the issues even more with other error handling and robustness measures way beyond what is required or even sensible to include here. I decided not to.

But, thanks for the constructive input @seeplus, please pass the soap to @jlb at the buffer police.
Well I did resolve the problem with the retrieveData(), and it's working. The only problem now is that the last contact is input, if the user search for that one it print it twice instead of 1 time and then returns to the main menu.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
/// Data Base
#include <stdio.h>
#include <windows.h>
#include <string.h>

void inputData();
void retrieveData();

struct Info
{
    char name[50];
    char lastname[50];
    char city[50];
    char country[50];
    char tel[20];
}info;

int main()
{
    int temp, status;
    unsigned options;
    do
    {
        printf("\n\t\t\t\tData Base\n\t\t\t\t---------");
        printf("\n\n\n\tChoose an option:\n\n");
        printf("\n 1. Input info");
        printf("\n 2. Retrieve info");
        printf("\n 3. Exit Data Base");
        printf("\n\n Select: ");
        status = scanf("%d", &options);

        while((temp = getchar()) != EOF && temp != '\n');
            if(!status)
            {
                printf("\n\n Invalid option.. Choose 1, 2 or 3\a");
                Sleep(2000);
                system("cls");
                options = 0;
                continue;
            }
            else if(options == 1)
            {
                inputData();
            }
            else if(options == 2)
            {
                retrieveData();
            }
            else if(options == 3)
            {
                printf("\n\n\t\t\t    Exiting Data Base");
                Sleep(2000);
                system("cls");
            }
            else
            {
                printf("\n\n Invalid option.. Choose 1, 2 or 3\a");
                Sleep(2000);
                system("cls");
            }
    }while(options != 3);

    return 0;
}

void inputData()
{
    FILE *file;
    file = fopen("records.txt", "a");

    if(!file)
    {
        printf("\n File could not be open: \n\n\a");
        Sleep(1000);
        printf(" %s", strerror(errno));
        Sleep(2500);
        system("cls");
        return;
    }

    system("cls");
    printf("\n\t\t\t\tData Base\n\t\t\t\t---------");
    printf("\n\n Enter info");
    printf("\n\n\n\tName:       ");
    scanf("%s", info.name);
    printf("\n\n\tLast name:  ");
    scanf("%s", info.lastname);
    printf("\n\n\tCity:       ");
    scanf("%s", info.city);
    printf("\n\n\tCountry:    ");
    scanf("%s", info.country);
    printf("\n\n\tPhone:      ");
    scanf("%s", info.tel);

    fprintf(file, "%s %s %s %s %s\n", info.name, info.lastname, info.city, info.country, info.tel);

    fclose(file);

    printf("\n\n\n Contact saved to ledger\n\n"
           " ..returning to main menu");
    Sleep(2500);
    system("cls");
}

void retrieveData()
{
    FILE *file = fopen("records.txt", "r");
    int found = 0;
    char tmp[50];

    if(!file)
    {
        printf("\n File could not be open: \n\n\a");
        Sleep(1000);
        printf(" %s", strerror(errno));
        Sleep(2500);
        system("cls");
        return;
    }

    system("cls");
    printf("\n\t\t\t\tData Base\n\t\t\t\t---------");
    printf("\n\n Search info");
    printf("\n\n\n\tName:       ");
    scanf("%s", tmp);

    while(!feof(file))
    {
        fscanf(file, "%s%s%s%s%s", info.name, info.lastname, info.city, info.country, info.tel);
        if(strcmp(tmp, info.name) == 0)
        {
            printf("\n\n Info found:\n\n  Last name:  %s\n  City:\t      "
                   "%s\n  Country:    %s\n  Phone:      %s\n\n",
                    info.lastname, info.city, info.country, info.tel);
            found = 1;
            Sleep(7000);
            printf("\n\n ..returning to main menu");
            Sleep(3000);
            system("cls");
        }
    }
    if(!found)
    {
        printf("\n\n Info not found\n\a");
        Sleep(2500);
        printf("\n\n ..returning to main menu");
        Sleep(2000);
        system("cls");
    }
    fclose(file);
}

However I wanna thank you all for your contribution and knowledge. I am a beginner, I did understand few things from all of you, but I need to read up more cause I don't have a teacher I did all on my own and from what I read about C programming, cause I like it very much, but it will take much time to fully understand the C language.

Thank you all !!!
So you just felt you would ignore the given advice/warnings about never using (f)scanf() with "%s", and that as a minimum "%ns" should be used where n is at least 1 less than the size of the char array ?? !!
Umm no no.. I read that but as I mentioned before I'm a beginner and I need time to understand things and why fscanf needs this "%ns" format, I think I might jump with my eyes over this important thing.. I'll change that and I'll read a bit about it. :)
Thanks seeplus
 
fscanf(file, "%ns%ns%ns%ns%ns", info.name, info.lastname, info.city, info.country, info.tel);


I did change it, but I get these warnings:

||=== Build: Debug in ProiectExercitiiC (compiler: GNU GCC Compiler) ===|
||In function 'retrieveData':|
|130|warning: format '%n' expects argument of type 'int *', but argument 3 has type 'char *' [-Wformat=]|
|130|warning: format '%n' expects argument of type 'int *', but argument 4 has type 'char *' [-Wformat=]|
|130|warning: format '%n' expects argument of type 'int *', but argument 5 has type 'char *' [-Wformat=]|
|130|warning: format '%n' expects argument of type 'int *', but argument 6 has type 'char *' [-Wformat=]|
|130|warning: format '%n' expects argument of type 'int *', but argument 7 has type 'char *' [-Wformat=]|
||=== Build finished: 0 error(s), 5 warning(s) (0 minute(s), 0 second(s)) ===|
n should be a value 1 less than the size of the array. So for info.name this would be 49. Replace the n with the actual value.

 
fscanf(file, "%49s%49s%49s%49s%49s", info.name, info.lastname, info.city, info.country, info.tel);


and similar for other uses of %s. Don't just replace %s by %49s everywhere. Check the size of the char array. The n is 1 less than the size. So if you had:

 
char test[38];


then you would use

 
fscanf(file, "%37s", test);

Ou, ou got it... :)

Thanks seeplus for the information and for the patience, most of the old programmers probably will ignore me for such a thing.. I'll go and change that..
I modify that and understand why, now it works.

The problem is still the same, if the last contact saved is searched the program print it 2 times instead 1 and then returns to main menu.
I open the records.txt file and I deleted manually the new line that was created at the EOF.
and now I'm running the program and works 100%.

My question is how to avoid the new line to the end of file ?
My question is how to avoid the new line to the end of file ?
The problem is that you check eof before fscanf(...). But fscanf(...) is actually the function that raises the eof. So you need to check it after the fscanf(...) call.

Alternatively you can use the return value of fscanf(...) to determine whether reading was sucessfull. The result needs to be exactly the number of items you are trying to read (in your case 5). Otherwise the data is invalid.

For the result of fscanf(...) read:

http://www.cplusplus.com/reference/cstdio/fscanf/?kw=fscanf

Your loop may look like:
1
2
3
4
5
    while(fscanf(file, "%s%s%s%s%s", info.name, info.lastname, info.city, info.country, info.tel) == 5)
    {
        if(strcmp(tmp, info.name) == 0)
....
    }
Okay.. now I am really happy... Thanks coder777 for the explanation you were the icing on the cake... works perfectly.

Thank you all guys...I couldn't do this without your help, I really appreciate your answers and explanations..
Until the next time cheers ! :)
The general problem with EOF is that you don't know you're at the end of the file until you try to read past it.
Thanks seeplus for the information and for the patience, most of the old programmers probably will ignore me for such a thing..

You're welcome. We were all beginners once upon a time..

Using feof() [or it's corresponding C++ .eof()] is nearly always not what is required. In almost all cases the result of the file operation should be checked for 'not success' in that particular case.

As @coder777 says, eof doesn't become true when an operation reaches the end of the file, but when the next operation tries to read past the eof.
Last edited on
I remember that argument! eof() is certainly not the devil and has its practical uses.
I remember that argument! eof() is certainly not the devil and has its practical uses.


IMO, the "practical uses" are rare and you really should prefer using "read operations" to control a data input loop, in C this means always checking the return values of the standard functions like scanf(). Properly using EOF takes care to insure you check all the other possible causes of failures as well, along with proper sequencing of the reads so that EOF triggers at the proper time.
I've had occasions where removing eof() would mean restructuring the code and making it more complicated than it has to be.

In the times where I've used eof(), it didn't make sense to make a loop condition on cin or getline, and nothing broke the program. I wish I had those examples on hand, but eof() has been very useful to me plenty of times.
Topic archived. No new replies allowed.
Pages: 123