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 in int 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 than gets()
that way. If status
was 20
characters in size, you would need to use "%19s"
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 with fgets()
anyway and then use sscanf()
to separate the values.
- pass the open
fptr
as an argument to add_task()
. That way you open the file (and validate the file is open) in main()
. 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 of fclose()
to catch any error that may have occurred since your last call to fprintf()
.
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.