๐ Project Overview โ What We Are Building
A command-line personal diary that saves entries permanently to a file called diary.txt. Every entry is automatically stamped with the current date and time. You can write new entries, read all past entries, search by keyword, count how many entries exist, and clear the diary. Every feature teaches a different File I/O function.
diary.txt โ persistent storage
fopen "a" โ append entries
fgets โ read line by line
strstr โ keyword search
time.h โ auto timestamp
remove() โ delete file
how data flows through the diary program
User types entry
โ
time() timestamp
โ
fprintf โ diary.txt
โ
fgets reads back
โ
strstr searches
The file is the database. Every function opens โ operates โ closes. The file persists between program runs โ entries accumulate across sessions.
step 1 โ file modes explained
S1
๐ File Modes โ fopen, fclose and the Six Modes
Every file operation starts with fopen โ the mode string controls everything
File Modes
fopen(filename, mode) returns a FILE * pointer โ a handle to the open file. If it returns NULL, the file could not be opened (wrong path, no permission, disk full). Always check for NULL before using the pointer. After every operation, fclose(fp) flushes the buffer and releases the file โ skipping it can cause data loss. The mode string is the key decision โ it controls whether you read, write, or append, and whether the file is text or binary.
| Mode | Meaning | File exists? | File missing? | Used for |
|---|---|---|---|---|
| "r" | Read text | Opens from start | Returns NULL | Reading existing file |
| "w" | Write text | Erases contents! | Creates new file | Fresh write โ overwrites |
| "a" | Append text | Adds to end | Creates new file | Adding entries โ diary โ |
| "r+" | Read+Write | Opens from start | Returns NULL | Update existing file |
| "rb" | Read binary | Opens bytes | Returns NULL | Images, binary data |
| "wb" | Write binary | Erases! | Creates new | Binary output |
- 1Open:
FILE *fp = fopen("diary.txt", "a")โ"a"appends without erasing old entries. Creates file if it does not exist. - 2Check NULL:
if (!fp) { printf("Error\n"); return; }โ never skip this. A NULL pointer crash is silent and confusing. - 3Close:
fclose(fp)โ flushes the internal buffer to disk. Without this, your last write may not be saved.
#include <stdio.h> int main() { /* "a" โ append: creates if missing, adds to end if exists */ FILE *fp = fopen("diary.txt", "a"); if (!fp) { /* ALWAYS check NULL */ printf("Error: cannot open diary.txt\n"); return 1; } fprintf(fp, "Test line written to diary.\n"); fclose(fp); /* flush + release */ printf("Written. diary.txt now exists.\n"); /* Open same file for reading */ fp = fopen("diary.txt", "r"); if (!fp) { printf("Read error\n"); return 1; } char line[256]; while (fgets(line, sizeof(line), fp)) printf("%s", line); fclose(fp); return 0; }
Written. diary.txt now exists. Test line written to diary.
Why "a" for a diary and not "w"? Mode
"w" truncates โ it deletes everything in the file before writing. One wrong call and your entire diary history is gone. Mode "a" only adds to the end โ existing entries are always safe.step 2 โ write a diary entry
S2
โ๏ธ writeEntry() โ Timestamp + fprintf to File
time.h gives current date/time โ fprintf writes the formatted entry โ fgets reads user input
Write + Timestamp
Writing a diary entry does three things: (1) get the current date and time from
time.h, (2) read the user's text with fgets, and (3) write both to the file with fprintf. The entry is wrapped in a visual separator so entries are easy to distinguish when reading back. time(NULL) returns the current Unix timestamp. localtime() converts it to a readable struct. strftime() formats it as a human-readable string.
- 1Get timestamp:
time_t now = time(NULL); struct tm *t = localtime(&now);โ gives year, month, day, hour, minute, second. - 2Format date:
strftime(buf, sizeof(buf), "%d %b %Y %H:%M", t)โ produces "04 Jul 2026 14:30" style string. - 3Read input:
fgets(entry, sizeof(entry), stdin)โ reads the full line including spaces. Safer thanscanf. - 4Write to file:
fprintf(fp, "...", dateBuf, entry)โ formats and writes both the timestamp and the user's text.
#include <stdio.h> #include <time.h> #include <string.h> #define DIARY "diary.txt" #define MAXLINE 512 void writeEntry() { /* 1. Get current date and time */ time_t now = time(NULL); struct tm *t = localtime(&now); char stamp[64]; strftime(stamp, sizeof(stamp), "%d %b %Y %H:%M:%S", t); /* 2. Read entry from user */ char entry[MAXLINE]; printf("Write your entry (one paragraph):\n> "); fgets(entry, sizeof(entry), stdin); /* Strip trailing newline for clean storage */ entry[strcspn(entry, "\n")] = '\0'; /* 3. Open in append mode โ never erases old entries */ FILE *fp = fopen(DIARY, "a"); if (!fp) { printf("Error opening diary!\n"); return; } /* 4. Write formatted entry */ fprintf(fp, "========================================\n"); fprintf(fp, "DATE : %s\n", stamp); fprintf(fp, "----------------------------------------\n"); fprintf(fp, "%s\n", entry); fprintf(fp, "========================================\n\n"); fclose(fp); /* flush to disk โ critical! */ printf("Entry saved on %s\n", stamp); } int main() { writeEntry(); writeEntry(); /* second entry accumulates โ "a" mode */ return 0; }
======================================== DATE : 04 Jul 2026 09:15:32 ---------------------------------------- Started learning File I/O in C today. Finally makes sense! ======================================== ======================================== DATE : 04 Jul 2026 09:16:10 ---------------------------------------- Wrote my first diary entry using fprintf. Feels great. ========================================
strcspn(entry, "\n") returns the index of the first newline character. Setting that position to '\0' strips the trailing newline that fgets always includes. Without this, every entry would have an extra blank line when read back.step 3 โ read all entries
S3
๐ readAll() โ fgets Line by Line Until EOF
Open in "r" mode โ fgets reads one line per call โ loop until it returns NULL
Read + fgets
Reading uses mode
"r" and fgets(line, size, fp). fgets reads up to size-1 characters or until a newline โ whichever comes first โ and always null-terminates the result. When it reaches the end of file it returns NULL. The loop while(fgets(line, sizeof(line), fp)) reads every line until EOF โ the most idiomatic C file-reading pattern. We also number the entries as we display them by counting the separator lines.
- 1Open "r":
fopen(DIARY, "r")โ if NULL, diary is empty or missing. Print a friendly message. - 2fgets loop:
while(fgets(line, sizeof(line), fp))โ each call fillslinewith the next line. Returns NULL at EOF. - 3Count entries: detect the
"========"separator line withstrncmpto increment an entry counter. - 4fclose: always close the read handle when done โ even for read-only files.
#include <stdio.h> #include <string.h> #define DIARY "diary.txt" #define MAXLINE 512 void readAll() { FILE *fp = fopen(DIARY, "r"); if (!fp) { printf("Diary is empty โ no entries yet.\n"); return; } char line[MAXLINE]; int entryNum = 0; printf("\n===== YOUR DIARY =====\n\n"); /* fgets returns NULL at EOF โ standard read loop */ while (fgets(line, sizeof(line), fp)) { /* Count entry start by detecting separator */ if (strncmp(line, "========", 8) == 0) { entryNum++; printf("--- Entry #%d ---\n", entryNum); continue; /* skip the raw separator line */ } printf("%s", line); /* print each line as-is */ } fclose(fp); printf("\nTotal entries found: %d\n", entryNum / 2); /* divide by 2: each entry has opening + closing separator */ } int main() { readAll(); return 0; }
===== YOUR DIARY ===== --- Entry #1 --- DATE : 04 Jul 2026 09:15:32 ---------------------------------------- Started learning File I/O in C today. Finally makes sense! --- Entry #2 --- DATE : 04 Jul 2026 09:16:10 ---------------------------------------- Wrote my first diary entry using fprintf. Feels great. Total entries found: 2
fgets vs fscanf for reading:
fscanf stops at whitespace โ useless for sentences. fgets reads the entire line including spaces, making it the right tool for any text that might contain spaces. Use fscanf only for structured numeric data like CSV files.step 4 โ search entries
S4
๐ searchDiary() โ strstr Keyword Match While Reading
Read line by line โ strstr checks each line โ print matching entries in full
Search + strstr
Search combines file reading with string matching. We read the diary line by line and use
strstr(line, keyword) to check whether each line contains the search term. strstr returns a pointer to the first occurrence in the string, or NULL if not found. The trick is tracking which entry we are in โ we buffer the current entry's lines so we can print the entire entry when a match is found inside it, not just the matching line.
- 1Buffer current entry: accumulate lines between separators into a temp buffer. When a match is found, print the whole buffer.
- 2
strstr(line, keyword): returns non-NULL if keyword is found anywhere in the line โ case-sensitive. Set amatchedflag. - 3Print full entry on match: when closing separator reached and
matched == 1, print the entire buffered entry. - 4Reset buffer at each opening separator to prepare for the next entry.
#include <stdio.h> #include <string.h> #define DIARY "diary.txt" #define MAXLINE 512 #define MAXENTRY 2048 void searchDiary(const char *keyword) { FILE *fp = fopen(DIARY, "r"); if (!fp) { printf("Diary is empty.\n"); return; } char line[MAXLINE]; char buffer[MAXENTRY]; /* accumulate one entry */ int matched = 0; int found = 0; int inEntry = 0; int openSep = 0; buffer[0] = '\0'; printf("\nSearching for: \"%s\"\n", keyword); printf("================================\n"); while (fgets(line, sizeof(line), fp)) { if (strncmp(line, "========", 8) == 0) { if (inEntry) { /* closing separator โ entry complete */ if (matched) { printf("%s\n", buffer); /* print full entry */ found++; } buffer[0] = '\0'; matched = 0; inEntry = 0; } else { inEntry = 1; /* opening separator */ } continue; } /* Accumulate lines in buffer */ strncat(buffer, line, MAXENTRY - strlen(buffer) - 1); /* strstr โ check if keyword appears in this line */ if (strstr(line, keyword)) matched = 1; } fclose(fp); if (!found) printf("No entries found containing \"%s\".\n", keyword); else printf("Found in %d entr%s.\n", found, found == 1 ? "y" : "ies"); } int main() { searchDiary("fprintf"); searchDiary("weather"); /* not in diary */ return 0; }
Searching for: "fprintf" ================================ DATE : 04 Jul 2026 09:16:10 ---------------------------------------- Wrote my first diary entry using fprintf. Feels great. Found in 1 entry. Searching for: "weather" ================================ No entries found containing "weather".
strstr(line, keyword) is case-sensitive โ "C" and "c" are different. For case-insensitive search, convert both line and keyword to lowercase copies using tolower() from ctype.h before comparing. The project works correctly as-is for exact matches.step 5 โ count and stats
S5
๐ diaryStats() โ Count Entries, Words, Characters
One pass through the file โ count separators, words, chars โ print summary
Stats + fgetc
Statistics require one full read pass. We count entries by detecting separator lines with
strncmp. We count total characters using fgetc โ which reads one character at a time and returns EOF at end of file. We count words by detecting transitions from whitespace to non-whitespace. We also add a clear diary function using remove() โ the standard library function that deletes a file by name.
- 1Count entries: every pair of
"========"separators = one entry. Count opening separators only. - 2Count words: walk char by char. Track
inWordflag โ flip from 0โ1 on non-space = new word. Total word count when flag goes 0โ1. - 3Count chars:
fgetc(fp)loop โ increment counter for every non-EOF character returned. - 4Clear diary:
remove("diary.txt")โ deletes the file entirely. Returns 0 on success, non-zero on failure.
#include <stdio.h> #include <string.h> #include <ctype.h> #define DIARY "diary.txt" #define MAXLINE 512 void diaryStats() { FILE *fp = fopen(DIARY, "r"); if (!fp) { printf("Diary is empty.\n"); return; } int entries = 0, words = 0, chars = 0, lines = 0; int inWord = 0; char line[MAXLINE]; while (fgets(line, sizeof(line), fp)) { lines++; /* Count entries via opening separator */ if (strncmp(line, "========", 8) == 0) { entries++; continue; /* skip separator from word/char count */ } if (strncmp(line, "--------", 8) == 0) continue; if (strncmp(line, "DATE", 4) == 0) continue; /* Walk each character โ count words + chars */ for (int i = 0; line[i] != '\0'; i++) { char c = line[i]; chars++; if (isspace((unsigned char)c)) { inWord = 0; } else { if (!inWord) { words++; inWord = 1; } } } } fclose(fp); printf("\n===== DIARY STATISTICS =====\n"); printf("Total entries : %d\n", entries / 2); printf("Total lines : %d\n", lines); printf("Total words : %d\n", words); printf("Total chars : %d\n", chars); printf("Avg words/entry: %.1f\n", entries ? (float)words / (entries/2) : 0); } /* Delete the diary file */ void clearDiary() { if (remove(DIARY) == 0) printf("Diary cleared.\n"); else printf("Could not clear โ diary may not exist.\n"); } int main() { diaryStats(); return 0; }
===== DIARY STATISTICS ===== Total entries : 2 Total lines : 12 Total words : 21 Total chars : 112 Avg words/entry: 10.5
remove(DIARY) is permanent. It deletes the file from disk โ there is no undo, no recycle bin. Always confirm with the user before calling it. In the full program we add a "Are you sure? (y/n)" prompt before proceeding.step 6 โ complete program
S6
๐ Complete Diary Program โ Menu-Driven Full App
All five functions + switch menu โ persistent across runs โ real working diary
Full Program
All five functions unified under a clean switch-case menu loop. The program runs until the user chooses Exit. Because we always use
"a" mode for writing and "r" for reading, every entry ever written accumulates in diary.txt โ even across separate program runs. The file is the permanent memory.
#include <stdio.h> #include <stdlib.h> #include <string.h> #include <time.h> #include <ctype.h> #define DIARY "diary.txt" #define MAXLINE 512 #define MAXENTRY 4096 /* โโ WRITE โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ */ void writeEntry() { time_t now = time(NULL); struct tm *t = localtime(&now); char stamp[64]; strftime(stamp, sizeof(stamp), "%d %b %Y %H:%M:%S", t); char entry[MAXLINE]; printf("\nWrite your entry:\n> "); fgets(entry, sizeof(entry), stdin); entry[strcspn(entry, "\n")] = '\0'; FILE *fp = fopen(DIARY, "a"); if (!fp) { printf("Error!\n"); return; } fprintf(fp, "========================================\n" "DATE : %s\n" "----------------------------------------\n" "%s\n" "========================================\n\n", stamp, entry); fclose(fp); printf("Entry saved. [%s]\n", stamp); } /* โโ READ ALL โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ */ void readAll() { FILE *fp = fopen(DIARY, "r"); if (!fp) { printf("\nDiary is empty.\n"); return; } char line[MAXLINE]; int n = 0, open = 0; printf("\n===== ALL ENTRIES =====\n"); while (fgets(line, sizeof(line), fp)) { if (strncmp(line, "========", 8) == 0) { if (!open) { n++; printf("\n[Entry #%d]\n", n); } open = !open; continue; } printf("%s", line); } fclose(fp); printf("\n--- %d total entr%s ---\n", n, n==1?"y":"ies"); } /* โโ SEARCH โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ */ void searchDiary() { char kw[64]; printf("\nSearch keyword: "); fgets(kw, sizeof(kw), stdin); kw[strcspn(kw, "\n")] = '\0'; FILE *fp = fopen(DIARY, "r"); if (!fp) { printf("Diary is empty.\n"); return; } char line[MAXLINE], buf[MAXENTRY]; int found=0, matched=0, open=0; buf[0] = '\0'; printf("\nResults for \"%s\":\n" "================================\n", kw); while (fgets(line, sizeof(line), fp)) { if (strncmp(line, "========", 8) == 0) { if (open && matched) { printf("%s\n", buf); found++; } open = !open; buf[0]='\0'; matched=0; continue; } strncat(buf, line, MAXENTRY-strlen(buf)-1); if (strstr(line, kw)) matched=1; } fclose(fp); printf(found ? "Found in %d entr%s.\n" : "No matches found.\n", found, found==1?"y":"ies"); } /* โโ STATS โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ */ void diaryStats() { FILE *fp = fopen(DIARY, "r"); if (!fp) { printf("\nDiary is empty.\n"); return; } char line[MAXLINE]; int entries=0,words=0,chars=0,inWord=0; while (fgets(line, sizeof(line), fp)) { if(strncmp(line,"========",8)==0){entries++;continue;} if(strncmp(line,"--------",8)==0||strncmp(line,"DATE",4)==0)continue; for(int i=0;line[i];i++){ chars++; if(isspace((unsignedchar)line[i])) inWord=0; else if(!inWord){words++;inWord=1;} } } fclose(fp); printf("\n===== DIARY STATS =====\n" "Entries : %d\nWords : %d\n" "Chars : %d\nAvg wds : %.1f\n", entries/2, words, chars, entries ? (float)words/(entries/2) : 0); } /* โโ CLEAR โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ */ void clearDiary() { char c; printf("\nDelete ALL entries? (y/n): "); scanf(" %c", &c); getchar(); if (c == 'y' || c == 'Y') printf(remove(DIARY)==0 ? "Diary cleared.\n" : "Error clearing diary.\n"); else printf("Cancelled.\n"); } /* โโ MENU โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ */ void showMenu() { printf("\nโโโโโโโโโโโโโโโโโโโโโโโโโโโโ\n" "โ ๐ PERSONAL DIARY โ\n" "โ โโโโโโโโโโโโโโโโโโโโโโโโโโโฃ\n" "โ 1. Write new entry โ\n" "โ 2. Read all entries โ\n" "โ 3. Search by keyword โ\n" "โ 4. Diary statistics โ\n" "โ 5. Clear diary โ\n" "โ 6. Exit โ\n" "โโโโโโโโโโโโโโโโโโโโโโโโโโโโ\n" "Choice: "); } int main() { int choice; do { showMenu(); scanf("%d", &choice); getchar(); switch (choice) { case 1: writeEntry(); break; case 2: readAll(); break; case 3: searchDiary(); break; case 4: diaryStats(); break; case 5: clearDiary(); break; case 6: printf("Goodbye!\n"); break; default: printf("Invalid choice.\n"); } } while (choice != 6); return 0; }
โโโโโโโโโโโโโโโโโโโโโโโโโโโโ โ ๐ PERSONAL DIARY โ โ โโโโโโโโโโโโโโโโโโโโโโโโโโโฃ โ 1. Write new entry โ โ 2. Read all entries โ โ 3. Search by keyword โ โ 4. Diary statistics โ โ 5. Clear diary โ โ 6. Exit โ โโโโโโโโโโโโโโโโโโโโโโโโโโโโ Choice: 1 Write your entry: > Completed the File I/O lesson today. Loved the diary project! Entry saved. [04 Jul 2026 14:22:18] Choice: 4 ===== DIARY STATS ===== Entries : 3 Words : 47 Chars : 263 Avg wds : 15.7 Choice: 3 Search keyword: lesson Results for "lesson": ================================ DATE : 04 Jul 2026 14:22:18 ---------------------------------------- Completed the File I/O lesson today. Loved the diary project! Found in 1 entry. Choice: 6 Goodbye!
Persistence across runs: close the program, run it again, choose option 2 โ all previous entries are still there. The file
diary.txt is the permanent memory. This is the core idea of File I/O โ data that outlives the program that created it.checklist โ tick each concept when understood
- S1 โ File Modes:
fopen(name, mode)returnsFILE*or NULL. Always check NULL."a"appends safely."w"erases."r"reads.fclose(fp)flushes to disk โ never skip it. - S2 โ Write Entry:
time(NULL)โlocaltime()โstrftime()for timestamp.fgets(entry, size, stdin)reads full line with spaces.strcspn(s,"\n")strips newline.fprintf(fp, ...)writes formatted text. - S3 โ Read All:
while(fgets(line, size, fp))โ reads until NULL (EOF). Standard file-read loop.strncmpdetects separator lines to count and format entries. - S4 โ Search:
strstr(line, keyword)returns pointer if found, NULL if not. Buffer current entry between separators. Print full entry only when match found inside it. - S5 โ Stats: Word counting via
inWordflag โ flip 0โ1 on non-space = new word.isspace()fromctype.h.remove(filename)deletes file permanently โ always confirm before calling. - S6 โ Full Program: Menu loop with
do-while+switch.getchar()afterscanfclears the newline from input buffer โ preventsfgetsfrom reading an empty line. Persistent file survives between program runs.