You have a number of problems with validation that prevent detecting when there are errors with file open, file read, file write and file close. Before getting there, if you need constants, #define
them or use an enum
to define them, e.g.
enum { NAMSZ = 15, MAXC = 512 };
(that way you don't have magic numbers sprinkled throughout your code. note: scanf
field width modifiers are an exception -- they must be specified and cannot use variable or constant labels)
There is no need to declare typedef struct a {...
, you don't make use of a
, just use the typedef
of the struct:
typedef struct {
char name[NAMSZ];
char surname[NAMSZ];
int age;
} sid;
I've given you the link to Why is while ( !feof (file) ) always wrong?. Instead, declare a buffer sufficient to hold each line and read each line with fgets
and then parse the values you need from the line using sscanf
(or just a pointer and loop), e.g.
int binarycopy (char *s1, char *s2)
{
sid id = { .name = "" };
FILE *f, *g;
char buf[MAXC] = "";
f = fopen (s1, "r");
g = fopen (s2, "wb");
if (f == NULL || g == NULL) {
fprintf (stderr, "binarycopy: file open failed.\n");
exit (EXIT_FAILURE);
}
while (fgets (buf, MAXC, f)) {
if (sscanf (buf, "%14s %14s %d", id.name, id.surname, &id.age) == 3) {
if (fwrite (&id, sizeof id, 1, g) != 1) {
fprintf (stderr, "error: fwrite error.\n");
exit (EXIT_FAILURE);
}
}
else {
fprintf (stderr, "binarycopy error: invalid line format.\n");
exit (EXIT_FAILURE);
}
}
fclose (f);
if (fclose (g) == -1) {
fprintf (stderr, "error: on stream close.\n");
exit (EXIT_FAILURE);
}
return 1;
}
(note: the validation of fclose
after write. stream errors can occur that are not otherwise reported. Always validate fclose
after write)
While you should serialize the data written to the binary file above (e.g. check the strlen
of each name, write the length, then that number of chars, and then the age, for learning purposes, you can write a struct-at-a-time, but note: the data file is not guaranteed to work on another architecture or compiler due to differences in padding. You can write the struct and read it back in on the same compiler, but know serializing the data is the proper way to go.
For your read, simply do the reverse. fread
a struct
worth of data, and write the line to the text file, e.g.
int textcopy (char *s1, char *s2)
{
sid id;
FILE *f;
FILE *g;
f = fopen (s1, "rb");
g = fopen (s2, "w");
if (f == NULL || g == NULL) {
fprintf (stderr, "textcopy: file open failed.\n");
exit (EXIT_FAILURE);
}
while (fread (&id, sizeof id, 1, f) == 1)
fprintf (g, "%s %s %d\n", id.name, id.surname, id.age);
fclose (f);
if (fclose (g) == -1) {
fprintf (stderr, "error: on stream close.\n");
exit (EXIT_FAILURE);
}
return 1;
}
In main()
validate your options. If you don't get -b
or -t
handle the error. Also, there is no need to use strcmp
, just check if the second char in argv[1]
(e.g. argv[1][1]
) is 'b'
or 't'
, e.g.
int main (int argc, char *argv[])
{
if (argc < 4) {
printf ("Not enough arguments");
return 1;
}
if (argv[1][1] == 'b')
binarycopy (argv[2], argv[3]);
else if (argv[1][1] == 't')
textcopy (argv[2], argv[3]);
else
fprintf (stderr, "error: unrecognized option.\n");
return 0;
}
Putting it altogether, you can do something like:
#include <stdio.h>
#include <stdlib.h>
enum { NAMSZ = 15, MAXC = 512 };
typedef struct {
char name[NAMSZ];
char surname[NAMSZ];
int age;
} sid;
int binarycopy (char *s1, char *s2)
{
sid id = { .name = "" };
FILE *f, *g;
char buf[MAXC] = "";
f = fopen (s1, "r");
g = fopen (s2, "wb");
if (f == NULL || g == NULL) {
fprintf (stderr, "binarycopy: file open failed.\n");
exit (EXIT_FAILURE);
}
while (fgets (buf, MAXC, f)) {
if (sscanf (buf, "%14s %14s %d", id.name, id.surname, &id.age) == 3) {
if (fwrite (&id, sizeof id, 1, g) != 1) {
fprintf (stderr, "error: fwrite error.\n");
exit (EXIT_FAILURE);
}
}
else {
fprintf (stderr, "binarycopy error: invalid line format.\n");
exit (EXIT_FAILURE);
}
}
fclose (f);
if (fclose (g) == -1) {
fprintf (stderr, "error: on stream close.\n");
exit (EXIT_FAILURE);
}
return 1;
}
int textcopy (char *s1, char *s2)
{
sid id;
FILE *f;
FILE *g;
f = fopen (s1, "rb");
g = fopen (s2, "w");
if (f == NULL || g == NULL) {
fprintf (stderr, "textcopy: file open failed.\n");
exit (EXIT_FAILURE);
}
while (fread (&id, sizeof id, 1, f) == 1)
fprintf (g, "%s %s %d\n", id.name, id.surname, id.age);
fclose (f);
if (fclose (g) == -1) {
fprintf (stderr, "error: on stream close.\n");
exit (EXIT_FAILURE);
}
return 1;
}
int main (int argc, char *argv[])
{
if (argc < 4) {
printf ("Not enough arguments");
return 1;
}
if (argv[1][1] == 'b')
binarycopy (argv[2], argv[3]);
else if (argv[1][1] == 't')
textcopy (argv[2], argv[3]);
else
fprintf (stderr, "error: unrecognized option.\n");
return 0;
}
Example Input File
$ cat dat/nameage.txt
John Smith 30
Mary Jane 35
Dan Kane 55
Annie Adams 40
Example Use/Output
Copy to binary:
$ ./bin/filecopytb -b dat/nameage.txt dat/nameagecpy.bin
Copy to text:
$ ./bin/filecopytb -t dat/nameagecpy.bin dat/nameagecpy.txt
Compare:
$ diff dat/nameage.txt dat/nameagecpy.txt
Hexdump of binary:
$ hexdump -Cv dat/nameagecpy.bin
00000000 4a 6f 68 6e 00 00 00 00 00 00 00 00 00 00 00 53 |John...........S|
00000010 6d 69 74 68 00 00 00 00 00 00 00 00 00 00 00 00 |mith............|
00000020 1e 00 00 00 4d 61 72 79 00 00 00 00 00 00 00 00 |....Mary........|
00000030 00 00 00 4a 61 6e 65 00 00 00 00 00 00 00 00 00 |...Jane.........|
00000040 00 00 00 00 23 00 00 00 44 61 6e 00 00 00 00 00 |....#...Dan.....|
00000050 00 00 00 00 00 00 00 4b 61 6e 65 00 00 00 00 00 |.......Kane.....|
00000060 00 00 00 00 00 00 00 00 37 00 00 00 41 6e 6e 69 |........7...Anni|
00000070 65 00 00 00 00 00 00 00 00 00 00 41 64 61 6d 73 |e..........Adams|
00000080 00 00 00 00 00 00 00 00 00 00 00 00 28 00 00 00 |............(...|
cat of text:
$ cat dat/nameagecpy.txt
John Smith 30
Mary Jane 35
Dan Kane 55
Annie Adams 40
Look things over and let me know if you have further questions.