This is part of my code of a task management system and I want to write an entry to a file in the format: Math HW,Math,Do page 9-15,2022/04/02,TD
I will also need to append many lines like this so is it sensible to use 'w'? I'm still a beginner to C.
But with the code below the entry that's being written to the file looks like this:
Math
,ath HW
,o page 9-15
,2022/04/02,TD
I'm not sure why this is happening
This is my add_task function:
void add_task()
{
FILE *fptr;
char name[20];
char category[20];
char info[50];
char date[20];
char status[20];
char another = 'Y';
fptr = fopen("Tasks.txt","r");
if (fptr==NULL)
{
printf("\nSYSTEM ERROR");
fptr = fopen("Tasks.txt","w");
getchar();
return;
}
while ( another == 'Y'|| another == 'y')
{
fseek(fptr,0,SEEK_END);
printf("\nTo Add a new task enter the details below\n");
printf("Name of Task:");
getchar();
fgets(name,50,stdin);
printf("Category of Task:");
getchar();
fgets(category,50,stdin);
printf("Information about task(Max 49 characters):");
getchar();
fgets(info, 50, stdin);
printf("Due Date of Task(yyyy/mm/dd):");
scanf(" %s", date);
printf("Status of Task\n TD = To-Do\n IP = In Progress\n CT = Completed Task\nEnter Status:");
scanf(" %s", status);
fprintf(fptr,"%s,%s,%s,%s,%s", name, category, info, date, status);
printf("\nDo you want to add another record (Y/N)");
fflush(stdin);
another = getchar();
}
fclose(fptr);
printf("\n\n\tPress any key to exit");
exit(0);
CodePudding user response:
fgets()
in fgets(name,50,stdin);
reads AND includes the '\n'
as part of the buffer it fills (name
in this example).
To remove the '\n'
simply overwrite it with a '\0'
. To remove the newline from the end of any string, you can use:
name[strcspn (name, "\n")] = 0;
strcspn (name, "\n")
returns the number of characters in name that do NOT include "\n"
-- i.e. the index of the '\n'
at the end of name
. Use the same buf[strcspn(buf, "\n")] = 0;
after each buffer you fill with fgets()
.
You will need to #include <string.h>
to make the string functions available.
Short Example
There are several other areas to fix.
- First, never hardcode filenames in a function. You shouldn't have to re-compile your code just to read or write to a different filename. Pass the name in as an argument to your program (that's what
int argc, char **argv
are for inint main (int argc, char **argv) { .. }
. It's fine to provide"Tasks.txt"
as a default name to use if no argument is provided. - Never use
scanf()
and%s
without using a field-width modifier. It is no safer thangets()
that way. Ifstatus
was20
characters in size, you would need to use"s"
to prevent potential buffer overrun. - But don't use
scanf()
anyway to read plain text.fgets()
avoid many, many pitfalls for the new C programmer. Further, even if you do need numeric conversions, read withfgets()
anyway and then usesscanf()
to separate the values. - pass the open
fptr
as an argument toadd_task()
. That way you open the file (and validate the file is open) inmain()
. If you can't open the file in main, there is no need to make the function call in the first place. - when using
fclose()
after writing to the file, always check the return offclose()
to catch any error that may have occurred since your last call tofprintf()
.
Putting all those pieces together, you could do something like:
#include <stdio.h>
#include <string.h>
#define MAXC 50 /* if you neeed a constant, #define one (or more) */
void add_task (FILE *fptr)
{
char name[MAXC]; /* use constant to set buffer size */
char category[MAXC];
char info[MAXC];
char date[MAXC];
char status[MAXC];
char another[MAXC] = "Y"; /* let's you use fgets() here as well */
fseek (fptr,0,SEEK_END); /* only needed once for writes */
while ( another[0] == 'Y'|| another[0] == 'y') /* check 1st char */
{
/* unless you have a numeric conversion, fputs() will do */
fputs ("\nTo Add a new task enter the details below\n\n"
"Name of Task: ", stdout);
if (!fgets (name, MAXC, stdin)) { /* ALWAYS validate each input */
return;
}
name[strcspn (name, "\n")] = 0; /* trim \n from end of name */
fputs ("Category of Task: ", stdout);
if (!fgets (category, MAXC, stdin)) { /* ditto */
return;
}
category[strcspn (category, "\n")] = 0; /* ditto */
printf ("Information about task (Max %d characters): ", MAXC-1);
if (!fgets (info, MAXC, stdin)) { /* ditto */
return;
}
info[strcspn (info, "\n")] = 0; /* ditto */
fputs ("Due Date of Task(yyyy/mm/dd): ", stdout);
if (!fgets (date, MAXC, stdin)) { /* ditto */
return;
}
date[strcspn (date, "\n")] = 0; /* ditto */
fputs ("Status of Task\n TD = To-Do\n IP = In Progress\n"
" CT = Completed Task\nEnter Status: ", stdout);
if (!fgets (status, MAXC, stdin)) { /* ditto */
return;
}
status[strcspn (status, "\n")] = 0; /* ditto */
fprintf (fptr, "%s,%s,%s,%s,%s\n", name, category, info, date, status);
fputs ("\nDo you want to add another record (Y/N): ", stdout);
if (!fgets (another, MAXC, stdin)) { /* ditto */
return;
}
}
}
int main (int argc, char **argv) {
/* use filename provided as 1st argument ("Tasks.txt" default) */
FILE *fp = fopen (argc > 1 ? argv[1] : "Tasks.txt", "w");
if (!fp) { /* validate file open for writing */
perror ("file open failed");
return 1;
}
add_task (fp); /* add your tasks */
if (fclose (fp) == EOF) { /* always check close-after-write */
perror ("fclose(fptr)");
return 1;
}
}
Example Use
Adding two tasks:
$ ./bin/addtask
To Add a new task enter the details below
Name of Task: Math
Category of Task: HW
Information about task (Max 49 characters): pages 9-15
Due Date of Task(yyyy/mm/dd): 2022/04/02
Status of Task
TD = To-Do
IP = In Progress
CT = Completed Task
Enter Status: TD
Do you want to add another record (Y/N): Y
To Add a new task enter the details below
Name of Task: Science
Category of Task: HW
Information about task (Max 49 characters): pages 100-125
Due Date of Task(yyyy/mm/dd): 2022/05/01
Status of Task
TD = To-Do
IP = In Progress
CT = Completed Task
Enter Status: TD
Do you want to add another record (Y/N): n
Resulting File
$ cat Tasks.txt
Math,HW,pages 9-15,2022/04/02,TD
Science,HW,pages 100-125,2022/05/01,TD
Using Array of struct
and qsort()
by date
When you need to handle a collection of separate data as a single object, C provides a struct
that allows you to do it. You simply add all the variables that need to be treated as a single object to the struct. To handle multiple objects, you then create an array of struct.
To sort the array by any one variable in the struct, you simply write a qsort()
compare()
function. Simple enough, the prototype is:
int compare (const void *a, const void *b)
There a
and b
will be pointers to elements in your array. In your compare function, you simply cast them back to the type they are. In this case if you have an array of struct task
, then each element is a task
. Since a
and b
are pointers to task
, you just cast them back as task *x = a;
and task *y = b;
.
Now just write how you want the elements compared. Since you have a pointer to task
and you want to sort by the date
member, you can simply return strcmp (x->date, y->date);
The qsort()
compare()
function could then be:
int compare (const void *a, const void *b)
{
const task *x = a; /* a and b are pointers or elements in your array */
const task *y = b; /* so you cast back to type task* (pointer) */
return strcmp (x->date, y->date); /* simply use strcmp() for comparison */
}
You call qsort()
as:
qsort (arr, n, sizeof *arr, compare); /* sort arr by date - ascending */
Done, sorted.
Putting all the pieces together and extending your example, you could do:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define MAXC 50 /* if you neeed a constant, #define one (or more) */
typedef struct task {
char name[MAXC]; /* use constant to set buffer size */
char category[MAXC];
char info[MAXC];
char date[MAXC];
char status[MAXC];
} task;
int compare (const void *a, const void *b)
{
const task *x = a; /* a and b are pointers or elements in your array */
const task *y = b; /* so you cast back to type task* (pointer) */
return strcmp (x->date, y->date); /* simply use strcmp() for comparison */
}
void add_task (FILE *fptr)
{
task arr[MAXC] = {{ .name = "" }}; /* declare an array of 50 struct */
int n = 0; /* counter for array index */
char another[MAXC] = "Y"; /* let's you use fgets() here as well */
fseek (fptr,0,SEEK_END); /* only needed once for writes */
while (n < MAXC && (another[0] == 'Y'|| another[0] == 'y'))
{
/* unless you have a numeric conversion, fputs() will do */
fputs ("\nTo Add a new task enter the details below\n\n"
"Name of Task: ", stdout);
if (!fgets (arr[n].name, MAXC, stdin)) { /* ALWAYS validate each input */
return;
}
arr[n].name[strcspn (arr[n].name, "\n")] = 0; /* trim \n from end */
fputs ("Category of Task: ", stdout);
if (!fgets (arr[n].category, MAXC, stdin)) { /* ditto */
return;
}
arr[n].category[strcspn (arr[n].category, "\n")] = 0; /* ditto */
printf ("Information about task (Max %d characters): ", MAXC-1);
if (!fgets (arr[n].info, MAXC, stdin)) { /* ditto */
return;
}
arr[n].info[strcspn (arr[n].info, "\n")] = 0; /* ditto */
fputs ("Due Date of Task(yyyy/mm/dd): ", stdout);
if (!fgets (arr[n].date, MAXC, stdin)) { /* ditto */
return;
}
arr[n].date[strcspn (arr[n].date, "\n")] = 0; /* ditto */
fputs ("Status of Task\n TD = To-Do\n IP = In Progress\n"
" CT = Completed Task\nEnter Status: ", stdout);
if (!fgets (arr[n].status, MAXC, stdin)) { /* ditto */
return;
}
arr[n].status[strcspn (arr[n].status, "\n")] = 0; /* ditto */
n = 1; /* increment index after all good inputs */
fputs ("\nDo you want to add another record (Y/N): ", stdout);
if (!fgets (another, MAXC, stdin)) { /* ditto */
return;
}
}
qsort (arr, n, sizeof *arr, compare); /* sort arr by date - ascending */
puts ("\nWriting the following to file:\n");
for (int i = 0; i < n; i ) {
/* output to screen */
printf ("%s,%s,%s,%s,%s\n", arr[i].name, arr[i].category, arr[i].info,
arr[i].date, arr[i].status);
/* output to file */
fprintf (fptr, "%s,%s,%s,%s,%s\n", arr[i].name, arr[i].category,
arr[i].info, arr[i].date,
arr[i].status);
}
}
int main (int argc, char **argv) {
/* use filename provided as 1st argument ("Tasks.txt" default) */
FILE *fp = fopen (argc > 1 ? argv[1] : "Tasks.txt", "w");
if (!fp) { /* validate file open for writing */
perror ("file open failed");
return 1;
}
add_task (fp); /* add your tasks */
if (fclose (fp) == EOF) { /* always check close-after-write */
perror ("fclose(fptr)");
return 1;
}
}
Example Use/Output
$ ./bin/addtask2
To Add a new task enter the details below
Name of Task: Math
Category of Task: HW
Information about task (Max 49 characters): too many pages
Due Date of Task(yyyy/mm/dd): 2022/05/10
Status of Task
TD = To-Do
IP = In Progress
CT = Completed Task
Enter Status: TD
Do you want to add another record (Y/N): Y
To Add a new task enter the details below
Name of Task: Science
Category of Task: HW
Information about task (Max 49 characters): Lots of science
Due Date of Task(yyyy/mm/dd): 2022/05/01
Status of Task
TD = To-Do
IP = In Progress
CT = Completed Task
Enter Status: TD
Do you want to add another record (Y/N): n
Writing the following to file:
Science,HW,Lots of science,2022/05/01,TD
Math,HW,too many pages,2022/05/10,TD
As you can see above, the arr
is now sorted by the date
member of each struct.
CodePudding user response:
The function fgets()
reads input until newline, the newline is included in the string if the maximum length is not exceeded. You need to strip the newline from the end of strings entered via fgets()
.
Solutions to that at Removing trailing newline character from fgets() input