Advanced Topics
File Handling & Command Line Arguments
Master C File Handling — what a file is, why files, file operations and modes, all file functions in table format, binary files, random access, and command line arguments — using LEARNCODEGURU’s 6-level learning model.
Level 1 Basics Content
C File Handling Overview
Everything a program stores in variables disappears when it ends. A file keeps data on disk so it survives after the program exits. This level covers what a file is, why we need files, the file operations, a complete table of file functions, and command line arguments.
What is a file?
A file is a named collection of data stored permanently on a secondary storage
device (hard disk, SSD, pen drive). In C you never touch the disk directly — you work through a
stream, represented by a FILE * pointer.
#include <stdio.h>
FILE *fp; /* a file pointer */
fp = fopen("data.txt", "w"); /* open (connect the stream) */
if (fp == NULL) return 1; /* ALWAYS check */
fprintf(fp, "Hello file\n"); /* write */
fclose(fp); /* close (flush and disconnect) */
Two kinds of files in C:
| # | Aspect | Text file | Binary file |
|---|---|---|---|
| 1 | Stores | Human-readable characters | Raw bytes exactly as in memory |
| 2 | Mode string | "r", "w", "a" | "rb", "wb", "ab" |
| 3 | Number 12345 stored as | 5 characters | 4 bytes (an int) |
| 4 | Newline translation | Yes on Windows (\n ↔ \r\n) | None — bytes pass through |
| 5 | Viewable in Notepad | Yes | No — looks like garbage |
| 6 | Typical functions | fprintf, fscanf, fgets | fwrite, fread |
| 7 | Portable across machines | Yes | No — padding and endianness differ |
Three streams are already open when your program starts:
stdin— standard input (keyboard)stdout— standard output (screen)stderr— standard error (screen, unbuffered)
Why files?
- Persistence — data survives after the program ends and the machine restarts
- Large volumes — store far more than fits comfortably in RAM
- No re-entry — the user does not retype the same data on every run
- Sharing — one program writes, another reads; files connect programs
- Reporting — save results, logs, and invoices for later inspection
- Backup and transfer — copy, email, or archive the data
- Automated testing — feed a prepared input file instead of typing
File operations
Every file program follows the same five steps.
| # | Operation | Function(s) | Purpose |
|---|---|---|---|
| 1 | Declare a file pointer | FILE *fp; | Holds the connection to the file |
| 2 | Open | fopen | Connect the stream in a given mode |
| 3 | Write / Read | fprintf, fputs, fputc, fwrite / fscanf, fgets, fgetc, fread | Transfer data both ways |
| 4 | Position (optional) | fseek, ftell, rewind | Move within the file for random access |
| 5 | Close | fclose | Flush the buffer and release the handle |
Opening modes
| # | Mode | Meaning | If the file exists | If it does not exist |
|---|---|---|---|---|
| 1 | "r" | Read only | Opens at the beginning | Returns NULL |
| 2 | "w" | Write only | Erases all contents | Creates it |
| 3 | "a" | Append | Writes at the end | Creates it |
| 4 | "r+" | Read and write | Opens, contents preserved | Returns NULL |
| 5 | "w+" | Read and write | Erases all contents | Creates it |
| 6 | "a+" | Read and append | Reads anywhere, writes at the end | Creates it |
| 7 | "rb", "wb", "ab"… | Same modes, binary | No newline translation | Same as text counterpart |
"w" when you meant
"a". The file is emptied the instant fopen succeeds, before you write
a single byte.
File functions in table format
(a) Open and close
| # | Function | Prototype | Returns | Use |
|---|---|---|---|---|
| 1 | fopen | FILE *fopen(const char *name, const char *mode) | File pointer or NULL | Open a file |
| 2 | fclose | int fclose(FILE *fp) | 0 or EOF | Flush and close |
| 3 | freopen | FILE *freopen(const char *, const char *, FILE *) | File pointer or NULL | Redirect an existing stream |
| 4 | fflush | int fflush(FILE *fp) | 0 or EOF | Force buffered output to disk |
(b) Character and string I/O
| # | Function | Prototype | Returns | Use |
|---|---|---|---|---|
| 5 | fgetc | int fgetc(FILE *fp) | Character or EOF | Read one character |
| 6 | fputc | int fputc(int c, FILE *fp) | Character or EOF | Write one character |
| 7 | fgets | char *fgets(char *s, int n, FILE *fp) | s or NULL | Read one line safely |
| 8 | fputs | int fputs(const char *s, FILE *fp) | Non-negative or EOF | Write a string (no newline added) |
| 9 | getc / putc | Same as fgetc / fputc | Character or EOF | Macro versions, often faster |
(c) Formatted I/O
| # | Function | Prototype | Returns | Use |
|---|---|---|---|---|
| 10 | fprintf | int fprintf(FILE *fp, const char *fmt, ...) | Characters written | Formatted write |
| 11 | fscanf | int fscanf(FILE *fp, const char *fmt, ...) | Items successfully read | Formatted read |
(d) Block (binary) I/O
| # | Function | Prototype | Returns | Use |
|---|---|---|---|---|
| 12 | fwrite | size_t fwrite(const void *p, size_t size, size_t n, FILE *fp) | Items written | Write structures / arrays |
| 13 | fread | size_t fread(void *p, size_t size, size_t n, FILE *fp) | Items read | Read structures / arrays |
(e) Positioning and status
| # | Function | Prototype | Returns | Use |
|---|---|---|---|---|
| 14 | fseek | int fseek(FILE *fp, long off, int origin) | 0 on success | Jump to a position |
| 15 | ftell | long ftell(FILE *fp) | Current offset or -1L | Where am I in the file |
| 16 | rewind | void rewind(FILE *fp) | Nothing | Go back to the start |
| 17 | feof | int feof(FILE *fp) | Non-zero at end of file | Check after a failed read |
| 18 | ferror | int ferror(FILE *fp) | Non-zero if an error occurred | Distinguish error from end of file |
| 19 | remove | int remove(const char *name) | 0 on success | Delete a file |
| 20 | rename | int rename(const char *old, const char *new) | 0 on success | Rename or move a file |
fseek origins: SEEK_SET (start), SEEK_CUR (current position), SEEK_END (end).
Command line arguments
Instead of prompting the user, a program can receive values from the command line. Give
main two parameters.
int main(int argc, char *argv[]) {
/* argc = argument count */
/* argv = argument vector */
}
/* Running: ./a.out data.txt 25 */
/* argc = 3 */
/* argv[0] = "./a.out" (program name) */
/* argv[1] = "data.txt" */
/* argv[2] = "25" (a STRING) */
/* argv[3] = NULL */
| # | Item | Type | Meaning |
|---|---|---|---|
| 1 | argc | int | Number of arguments, always ≥ 1 |
| 2 | argv | char *[] | Array of strings |
| 3 | argv[0] | char * | Program name as invoked |
| 4 | argv[1] … | char * | The actual arguments |
| 5 | argv[argc] | char * | Always NULL |
/* Print all arguments, with validation */
#include <stdio.h>
int main(int argc, char *argv[]) {
int i;
if (argc < 2) {
printf("usage: %s arg1 arg2 ...\n", argv[0]);
return 1;
}
for (i = 0; i < argc; i++)
printf("argv[%d] = %s\n", i, argv[i]);
return 0;
}
Arguments are strings — convert them yourself:
#include <stdlib.h>
int n = atoi(argv[1]); /* string → int */
double d = atof(argv[2]); /* string → double */
./copy source.txt dest.txt needs no prompts at all, which also makes the program
usable inside scripts.
Sample program — write then read a text file
/* The complete open-write-close-open-read-close cycle */
#include <stdio.h>
int main(void) {
FILE *fp;
char line[200];
/* ---- write ---- */
fp = fopen("notes.txt", "w");
if (fp == NULL) {
perror("notes.txt");
return 1;
}
fprintf(fp, "C file handling\n");
fprintf(fp, "Roll = %d, Marks = %.2f\n", 101, 88.5);
fputs("last line\n", fp);
fclose(fp);
/* ---- read back ---- */
fp = fopen("notes.txt", "r");
if (fp == NULL) {
perror("notes.txt");
return 1;
}
printf("File contents:\n");
while (fgets(line, sizeof line, fp) != NULL)
printf("%s", line); /* fgets keeps the \n */
fclose(fp);
return 0;
}
fopen is checked, the read loop tests
fgets itself instead of feof, and both opens have a matching
fclose.
Level 2 Advanced Content
Going deeper into file handling
Level 1 covered files, modes, operations, the function table, and command line arguments.
Level 2 explains binary records, random access with fseek/ftell,
error handling, buffering, the feof trap, and common mistakes.
Binary files — fwrite and fread with structures
struct Student {
char name[50];
int roll;
float marks;
};
struct Student s = { "Asha", 101, 88.5 };
FILE *fp = fopen("rec.dat", "wb"); /* note the b */
fwrite(&s, sizeof s, 1, fp); /* returns 1 on success */
fclose(fp);
struct Student t;
fp = fopen("rec.dat", "rb");
if (fread(&t, sizeof t, 1, fp) == 1)
printf("%s %d %.2f\n", t.name, t.roll, t.marks);
fclose(fp);
- Both take size × count and return the count transferred
- A short return means end of file or an error — test it, do not ignore it
- Binary records are fixed size, which is what makes random access possible
- Not portable: padding and endianness differ between machines
Random access — fseek, ftell, rewind
fseek(fp, 0L, SEEK_SET); /* first byte */
fseek(fp, 0L, SEEK_END); /* just past the last byte */
fseek(fp, -10L, SEEK_CUR); /* 10 bytes back */
/* Size of a file in bytes */
fseek(fp, 0L, SEEK_END);
long size = ftell(fp);
rewind(fp); /* back to the start */
/* Jump straight to record number k (0-based) */
fseek(fp, k * (long)sizeof(struct Student), SEEK_SET);
fread(&t, sizeof t, 1, fp);
| Origin | Value | Offset measured from |
|---|---|---|
SEEK_SET | 0 | Beginning of the file |
SEEK_CUR | 1 | Current position |
SEEK_END | 2 | End of the file |
fseek instead of reading 4999 records first.
The feof trap
/* WRONG — prints the last value twice */
while (!feof(fp)) {
fscanf(fp, "%d", &n);
printf("%d\n", n);
}
/* RIGHT — the read itself is the condition */
while (fscanf(fp, "%d", &n) == 1)
printf("%d\n", n);
/* RIGHT — line based */
while (fgets(line, sizeof line, fp) != NULL)
fputs(line, stdout);
/* RIGHT — character based (note: int, not char) */
int ch;
while ((ch = fgetc(fp)) != EOF)
putchar(ch);
feof reports what already happened. It only turns true after a read has
failed, so a loop that tests it first always runs one extra iteration on stale data.
Error handling and buffering
#include <stdio.h>
#include <errno.h>
#include <string.h>
FILE *fp = fopen("missing.txt", "r");
if (fp == NULL) {
perror("missing.txt"); /* prints the OS reason */
fprintf(stderr, "%s\n", strerror(errno)); /* same text, as string */
return 1;
}
/* After a loop, tell end-of-file apart from a real error */
if (ferror(fp))
fprintf(stderr, "read error\n");
- Buffering — output sits in memory until the buffer fills,
fflush, orfclose stderris unbuffered so error messages appear immediatelyfflush(stdin)is undefined behaviour — flushing is for output- An abnormal exit without
fclosecan leave a file empty on disk
A real tool — copy using command line arguments
/* usage: ./copy source.txt dest.txt */
#include <stdio.h>
int main(int argc, char *argv[]) {
FILE *in, *out;
int ch;
if (argc != 3) {
fprintf(stderr, "usage: %s source dest\n", argv[0]);
return 1;
}
in = fopen(argv[1], "rb");
if (in == NULL) {
perror(argv[1]);
return 1;
}
out = fopen(argv[2], "wb");
if (out == NULL) {
perror(argv[2]);
fclose(in); /* close on the error path too */
return 1;
}
while ((ch = fgetc(in)) != EOF)
fputc(ch, out);
fclose(in);
fclose(out);
printf("copied %s to %s\n", argv[1], argv[2]);
return 0;
}
Common mistakes
| Mistake | Why it hurts | Fix |
|---|---|---|
Not checking fopen | Crash on a NULL stream | if (fp == NULL) { perror(...); return 1; } |
Using "w" to add data | Existing contents erased | Use "a" to append |
while (!feof(fp)) | Last record processed twice | Loop on the read call's return value |
char ch = fgetc(fp); | EOF may be indistinguishable from data | Declare int ch; |
Forgetting fclose | Buffered data never written | Close on every exit path |
fflush(stdin) | Undefined behaviour | Read and discard until \n |
Not stripping \n after fgets | String comparisons fail invisibly | s[strcspn(s, "\n")] = '\0'; |
| Mixing text and binary modes | Newline translation corrupts binary data | Use "rb" / "wb" for binary |
Reading argv[1] without checking argc | Reads past the array | Validate argc first |
Treating argv[1] as a number | It is a string | atoi / strtol |
Level 3 C Tricky Interview Points
Advanced tricky interview points on FILE pointers, modes, EOF handling, buffering, binary records, and command line arguments.
Tricky Interview Point-1:
FILE is an opaque structure — you always use FILE *, never FILE
FILE holds the buffer, position indicator, and error flags. The standard never says what is inside it, so you only ever handle the pointer that fopen returns.
Tricky Interview Point-2:
fopen returns NULL on failure — checking it is not optional
A missing file, a bad path, or no permission all return NULL. Using that NULL pointer with fprintf or fscanf is undefined behaviour and usually a crash.
Tricky Interview Point-3:
"w" truncates an existing file to zero length immediately
Opening with "w" destroys the previous contents the moment fopen succeeds, even if you never write anything. Use "a" to append or "r+" to update in place.
Tricky Interview Point-4:
"r+" and "w+" both allow reading and writing, but differ at open time
"r+" requires the file to exist and preserves it; "w+" creates or truncates. "a+" always writes at the end regardless of where you positioned the file.
Tricky Interview Point-5:
In text mode on Windows, \n is translated to \r\n on the way out
That is why the byte count on disk can exceed what you wrote, and why ftell offsets in text mode are only meaningful when fed back to fseek.
Tricky Interview Point-6:
getc/fgetc return int, not char — because of EOF
EOF is typically -1 and must be distinguishable from every valid byte including 0xFF. Storing the result in a char breaks the comparison on some platforms.
Tricky Interview Point-7:
while (!feof(fp)) is the classic wrong loop
feof only becomes true after a read has already failed, so the last item gets processed twice. Loop on the read call itself: while (fgets(...) != NULL) or while (fscanf(...) == 1).
Tricky Interview Point-8:
fgets keeps the newline; gets never existed safely and was removed
fgets stores the \n if there was room, so you usually strip it. gets had no bound and was removed in C11 — never use it.
Tricky Interview Point-9:
fread and fwrite work in units of size × count and return the count
fwrite(&s, sizeof s, 1, fp) returns 1 on success, not the byte count. A short return means a partial or failed transfer, which is how you detect a full disk.
Tricky Interview Point-10:
Binary records written by fwrite are not portable
Padding, endianness, and type sizes differ across compilers and machines. Binary files are fine for one program on one platform; use text for interchange.
Tricky Interview Point-11:
fflush is defined for output streams only
fflush(stdin) is undefined behaviour even though it appears in many textbooks. To discard leftover input, read and discard characters until \n or EOF.
Tricky Interview Point-12:
fclose flushes buffers — skipping it can silently lose data
Buffered output only reaches the disk on flush or close. A program that exits abnormally without closing may leave the file empty or truncated.
Tricky Interview Point-13:
argc is always at least 1, and argv[0] is the program name
Real arguments start at argv[1], and argv[argc] is guaranteed to be NULL. That trailing NULL lets you walk argv without using argc at all.
Tricky Interview Point-14:
Command line arguments are always strings
./a.out 25 passes the two characters "25", not the integer 25. Convert with atoi or, better, strtol so you can detect malformed input.
Tricky Interview Point-15:
The shell expands wildcards before your program starts
./a.out *.txt gives your program one argument per matching file, not the literal "*.txt". Quote the argument if you want the pattern itself.
Level 4 Simple Programs
Short programs covering writing, reading, counting, copying, binary records, and command line arguments.
Program 1 — Create a file and write lines into it
/* Write three lines to a text file */
#include <stdio.h>
int main(void) {
FILE *fp = fopen("data.txt", "w");
if (fp == NULL) {
perror("data.txt");
return 1;
}
fputs("Line one\n", fp);
fputs("Line two\n", fp);
fprintf(fp, "Line %d\n", 3);
fclose(fp);
printf("data.txt written\n");
return 0;
}
Program 2 — Display a file on screen
/* Character by character until EOF */
#include <stdio.h>
int main(void) {
FILE *fp = fopen("data.txt", "r");
int ch; /* int, not char */
if (fp == NULL) {
perror("data.txt");
return 1;
}
while ((ch = fgetc(fp)) != EOF)
putchar(ch);
fclose(fp);
return 0;
}
Program 3 — Count characters, words, and lines
/* A miniature wc */
#include <stdio.h>
#include <ctype.h>
int main(void) {
FILE *fp = fopen("data.txt", "r");
int ch, inWord = 0;
long chars = 0, words = 0, lines = 0;
if (fp == NULL) {
perror("data.txt");
return 1;
}
while ((ch = fgetc(fp)) != EOF) {
chars++;
if (ch == '\n')
lines++;
if (isspace(ch))
inWord = 0;
else if (!inWord) {
inWord = 1;
words++;
}
}
fclose(fp);
printf("chars = %ld\nwords = %ld\nlines = %ld\n", chars, words, lines);
return 0;
}
Program 4 — Append a line to an existing file
/* "a" keeps the old contents */
#include <stdio.h>
#include <string.h>
int main(void) {
FILE *fp;
char line[200];
printf("Text to append: ");
if (fgets(line, sizeof line, stdin) == NULL)
return 1;
line[strcspn(line, "\n")] = '\0'; /* strip the newline */
fp = fopen("data.txt", "a");
if (fp == NULL) {
perror("data.txt");
return 1;
}
fprintf(fp, "%s\n", line);
fclose(fp);
printf("appended\n");
return 0;
}
Program 5 — Binary file: write and read a structure
/* fwrite / fread with a record */
#include <stdio.h>
struct Student {
char name[50];
int roll;
float marks;
};
int main(void) {
struct Student s = { "Asha", 101, 88.5 };
struct Student t;
FILE *fp;
fp = fopen("rec.dat", "wb");
if (fp == NULL) { perror("rec.dat"); return 1; }
fwrite(&s, sizeof s, 1, fp);
fclose(fp);
fp = fopen("rec.dat", "rb");
if (fp == NULL) { perror("rec.dat"); return 1; }
if (fread(&t, sizeof t, 1, fp) == 1)
printf("%s %d %.2f\n", t.name, t.roll, t.marks);
else
printf("read failed\n");
fclose(fp);
return 0;
}
Program 6 — Sum of numbers given as command line arguments
/* usage: ./sum 10 20 30 */
#include <stdio.h>
#include <stdlib.h>
int main(int argc, char *argv[]) {
int i, sum = 0;
if (argc < 2) {
fprintf(stderr, "usage: %s n1 n2 ...\n", argv[0]);
return 1;
}
for (i = 1; i < argc; i++)
sum += atoi(argv[i]); /* string → int */
printf("sum of %d numbers = %d\n", argc - 1, sum);
return 0;
}
Level 5 Problem Solving Tricks
How to master file handling while solving problems
Nearly every file bug comes from four habits being missing: checking fopen, looping
on the read instead of feof, bounding fgets with
sizeof, and closing on every exit path.
Check fopen every single time
if (fp == NULL) { perror("open"); return 1; } is three lines that turn a mysterious crash into a clear message naming the actual OS error.
Loop on the read function, never on feof
while (fgets(line, sizeof line, fp)) and while (fscanf(fp, "%d", &n) == 1) both stop exactly at the end. Using feof as the condition duplicates the last record.
Pass sizeof the buffer to fgets, not a hard-coded number
fgets(line, sizeof line, fp) stays correct when the array size changes. A literal 100 becomes a buffer overflow the day someone shrinks the array.
Strip the trailing newline that fgets leaves behind
line[strcspn(line, "\n")] = '\0'; is the one-line idiom. Without it, comparisons and concatenations quietly fail on an invisible character.
Close every file on every exit path
If a function returns early on an error, close the file first. A single fclose at the bottom is only reached when nothing goes wrong.
Use "a" when you mean append
Reaching for "w" to add a line silently wipes the file. "a" positions every write at the end and creates the file if it is missing.
Validate argc before touching argv
if (argc < 2) { printf("usage: %s <file>\n", argv[0]); return 1; } prevents reading past the array and gives the user a usable message.
Prefer strtol over atoi for numeric arguments
atoi returns 0 for "abc" with no way to tell it from a real 0. strtol reports where parsing stopped so you can reject bad input.
Quick success checklist
- Before run: right mode?
fopenchecked? every open has anfclose? - Debug order: does the file exist → print the path → print each item as it is read → check
ferror - Safety habit:
fgetswithsizeof→ strip\n→ validateargcbeforeargv
feof, tell you when to stop.
Level 6 Practice Programs List
Programming questions only — focused on file handling: creating, reading, counting, copying, appending, binary records, random access, and command line arguments.
- 1. Write a C program that creates a text file, writes three lines into it, and closes it.
- 2. Write a C program that opens an existing text file and displays its contents on screen character by character.
- 3. Write a C program that reads a file line by line using fgets and prints each line with its line number.
- 4. Write a C program that counts the number of characters, words, and lines in a text file.
- 5. Write a C program that copies the contents of one file into another file.
- 6. Write a C program that appends a new line to an existing file without erasing its contents.
- 7. Write a C program that writes a structure (student record) to a binary file using fwrite and reads it back with fread.
- 8. Write a C program that reads n integers from a file and prints their sum and average.
- 9. Write a C program that uses fseek and ftell to find the size of a file in bytes.
- 10. Write a C program that searches a file for a given word and prints how many times it occurs.
- 11. Write a C program that takes two file names as command line arguments and copies the first into the second.
- 12. Write a C program that accepts numbers as command line arguments and prints their sum.