Reading and Writing Text Files
Programs keep data between runs by writing files. In C you open a file with fopen, which gives you a FILE *, read or write it with the same kinds of functions you use for the terminal, and close it with fclose.
Writing a file
#include <stdio.h> int main(void) { FILE *out = fopen("shopping.txt", "w"); if (out == NULL) { perror("shopping.txt"); return 1; } fprintf(out, "eggs,12\n"); fprintf(out, "milk,2\n"); fputs("bread,1\n", out); if (fclose(out) != 0) { perror("closing shopping.txt"); return 1; } printf("saved\n"); return 0; }
saved
fprintf and fputs work like printf and puts but take the file to write to. perror prints your message followed by the system's reason for the failure, such as "Permission denied" (lesson 50).
Opening modes
| Mode | Meaning | If the file exists | If it doesn't |
|---|---|---|---|
"r" | Read | Read from the start | Fails (NULL) |
"w" | Write | Erased, then written | Created |
"a" | Append | Writes go to the end | Created |
"r+", "w+", "a+" | Read and write | As above, but both directions allowed | |
"wx" (C11) | Write a new file | Fails — protects existing files | Created |
Add b for binary files ("rb", "wb"; lesson 48). On Linux and macOS text and binary modes behave the same; on Windows, text mode converts line endings.
Reading line by line
Here's a data file the next program reads:
apples,30,0.45 bananas,24,0.30 cherries,200,0.05 dates,15,0.80
#include <stdio.h> int main(void) { FILE *in = fopen("inventory.csv", "r"); if (in == NULL) { perror("inventory.csv"); return 1; } char line[128]; double total = 0; int line_no = 0; while (fgets(line, sizeof line, in) != NULL) { line_no++; char name[32]; int qty; double price; if (sscanf(line, "%31[^,],%d,%lf", name, &qty, &price) != 3) { fprintf(stderr, "line %d: bad format\n", line_no); continue; } printf("%-10s %4d x %.2f = %6.2f\n", name, qty, price, qty * price); total += qty * price; } fclose(in); printf("Total stock value: %.2f\n", total); return 0; }
apples 30 x 0.45 = 13.50 bananas 24 x 0.30 = 7.20 cherries 200 x 0.05 = 10.00 dates 15 x 0.80 = 12.00 Total stock value: 42.70
%31[^,] reads up to 31 characters that aren't commas — a handy way to split simple comma-separated data. This "read a line, then parse it" approach is the same one lesson 15 recommended for keyboard input.
Appending
#include <stdio.h> int log_event(const char *message) { FILE *f = fopen("events.log", "a"); if (f == NULL) return -1; fprintf(f, "%s\n", message); return fclose(f); } int main(void) { log_event("app started"); log_event("user signed in"); log_event("app stopped"); FILE *f = fopen("events.log", "r"); if (f == NULL) return 1; int c; while ((c = fgetc(f)) != EOF) { putchar(c); } fclose(f); return 0; }
app started user signed in app stopped
fgetc reads one character (as an int, like getchar), returning EOF at the end.
Reading words and numbers
fscanf reads formatted values directly from a file. It's convenient for simple, trusted formats; for anything that might be malformed, prefer fgets plus sscanf:
Ana 82 Ben 91 Chloe 77
#include <stdio.h> int main(void) { FILE *f = fopen("scores.txt", "r"); if (f == NULL) return 1; char name[32]; int score, count = 0, sum = 0; while (fscanf(f, "%31s %d", name, &score) == 2) { sum += score; count++; } fclose(f); printf("%d students, average %.1f\n", count, (double) sum / count); return 0; }
3 students, average 83.3
stdin, stdout and stderr
The three standard streams are FILE * values too, so any function that takes a file works with them. Writing a function that takes a FILE * lets it print to the screen or a file:
#include <stdio.h> void print_report(FILE *out, int sold, double revenue) { fprintf(out, "Units sold: %d\n", sold); fprintf(out, "Revenue: %.2f\n", revenue); } int main(void) { print_report(stdout, 42, 1234.5); // to the terminal FILE *f = fopen("report.txt", "w"); if (f != NULL) { print_report(f, 42, 1234.5); // same code, to a file fclose(f); } return 0; }
Units sold: 42 Revenue: 1234.50
Always close files
Output is buffered: written data collects in memory and goes to disk in chunks. fclose flushes the buffer and releases the file, and it can fail (for example, if the disk is full) — check its result when the data matters. fflush(f) forces buffered output out without closing.
Try it yourself
Copy a file line by line, adding line numbers.
Roses are red, pointers are tricky, free what you malloc or your memory gets sticky.
1 Roses are red, 2 pointers are tricky, 3 free what you malloc 4 or your memory gets sticky.