This is one of the most fundamental things you must do in any language:
- read data from a file, and
- parse that data into needed information.
In your case you have whitespace separate names and an ID in your input file. While you can use fscanf
directly, it is horribly fragile. If a single line does not match your format string, your read will fail with a matching failure, character extraction from the stream ceases, and you are then left with the remainder of the line in your input buffer to deal with before you can move forward.
For that reason, a better approach is the read each line into a buffer with fgets
and a sufficiently sized buffer (or using POSIX getline
) to consume an entire line of input with every read. The you can parse the needed information from the line stored in the buffer without affecting your read operation. This also provides the benefit of being able to independently validate your (1) read, and (2) the parse of information.
There are many ways to parse the needed information from the buffer. You can use sscanf
reading from the buffer (much like you would have used fscanf
on the input itself), you can walk-a-pair-of-pointers down the buffer, bracketing each word and then memcpy
and nul-terminate, you can use strtok
(but it modifies the original buffer), or you can use a combination of strspn
and strcspn
to bracket each word similar to walking the pointers.
In your case let's just use sscanf
since for a fixed format, it is just as easy. To store your 3-strings worth of name, last, id
, create a struct with those members, then you can create an array of struct (we will leave the dynamic array, or linked-list for later), and you can store all names and IDs you read, for example:
#include <stdio.h>
#define MAXID 16 /* if you need a constant, #define one (or more) */
#define MAXNM 32
#define MAXPN 128
#define MAXC 1024
typedef struct {
char name[MAXNM],
last[MAXNM],
id[MAXID];
} typeperson;
You now have a struct (with a convenient typedef
to typeperson
you can use to create an array of struct (with each array initialized all zero), e.g.
int main (int argc, char **argv) {
char buf[MAXC];
size_t n = 0;
typeperson person[MAXPN] = {{"", "", ""}};
You now have an array of MAXPN
(128
) person to fill. Now simply open your file using the name provided as the first argument to your program (or read from stdin
by default if no argument is given) and validate the file is open for reading:
/* use filename provided as 1st argument (stdin by default) */
FILE *fp = argc > 1 ? fopen (argv[1], "r") : stdin;
if (!fp) { /* validate file open for reading */
perror ("file open failed");
return 1;
}
With your file open and validated, you can now read each line into buf
and then parse name, last, id
from buf
using sscanf
(all conversion specifiers except for "%c"
and "%[..]"
(and technically "%n"
, but that doesn't extract from the buffer) skip all leading whitespace allowing you to separate your name, last, id
regardless of the amount of whitespace between them:
/* protect array bounds and read each line into struct */
while (n < MAXPN && fgets (buf, MAXC, fp)) {
if (sscanf (buf, "%s %s %s",
person[n].name, person[n].last, person[n].id) == 3)
n++;
}
(note: the test of n < MAXPN
that protects your array bounds and prevents you from writing more elements than you have storage for)
What happens if the line has the wrong format? How do you recover? Simple. By consuming a line with each read, any line that doesn't match your sscanf
format string is quietly ignore and does not cause you any problem.
All that remains is closing the file and using your data in any way you need. Putting it together in a short example, you could do:
#include <stdio.h>
#define MAXID 16 /* if you need a constant, #define one (or more) */
#define MAXNM 32
#define MAXPN 128
#define MAXC 1024
typedef struct {
char name[MAXNM],
last[MAXNM],
id[MAXID];
} typeperson;
int main (int argc, char **argv) {
char buf[MAXC];
size_t n = 0;
typeperson person[MAXPN] = {{"", "", ""}};
/* use filename provided as 1st argument (stdin by default) */
FILE *fp = argc > 1 ? fopen (argv[1], "r") : stdin;
if (!fp) { /* validate file open for reading */
perror ("file open failed");
return 1;
}
/* protect array bounds and read each line into struct */
while (n < MAXPN && fgets (buf, MAXC, fp)) {
if (sscanf (buf, "%s %s %s",
person[n].name, person[n].last, person[n].id) == 3)
n++;
}
if (fp != stdin) fclose (fp); /* close file if not stdin */
for (size_t i = 0; i < n; i++) /* output the resutls */
printf ("person[%3zu] : %-20s %-20s %s\n",
i, person[i].name, person[i].last, person[i].id);
}
Example Input File
With an intentional line that does not match the format (e.g. "..."
):
$ cat dat/peopleid.txt
George Washington 1
John Adams 2
Thomas Jefferson 3
James Madison 4
...
Royal Embarrasment 45
Example Use/Output
$ ./bin/struct_person < dat/peopleid.txt
person[ 0] : George Washington 1
person[ 1] : John Adams 2
person[ 2] : Thomas Jefferson 3
person[ 3] : James Madison 4
person[ 4] : Royal Embarrasment 45
Look things over and let me know if you have any further questions.