Reading & Writing Files
Once a file is open, <stdio.h> offers several families of functions for moving text data in and out of it, at different levels of granularity: formatted, line-based, and character-based.
Formatted I/O: fprintf / fscanf
fprintf and fscanf are the file-based counterparts of printf and scanf — same format-specifier syntax, but they take a FILE * as their first argument telling them where to write to or read from.
FILE *fp = fopen("scores.txt", "w");
fprintf(fp, "%s %d\n", "Alice", 95);
fclose(fp);
/* later, reading it back */
FILE *in = fopen("scores.txt", "r");
char name[50];
int score;
fscanf(in, "%49s %d", name, &score);
fclose(in);Line-Based I/O: fgets / fputs
fgets reads an entire line (up to a size limit) into a buffer, including the trailing newline if it fits — it is the safe, bounds-checked way to read text a line at a time, unlike gets (which was removed from the standard for being unsafe). fputs writes a whole string, without adding a newline of its own.
Character-Based I/O: fgetc / fputc
fgetc reads a single character (returned as an int so it can also represent EOF), and fputc writes a single character. This is the lowest-level of the three, useful when you need to process a file one byte at a time.
A Full Worked Example
This program writes three lines to a file with fputs, then reads them back one line at a time with fgets and prints each to standard output.
#include <stdio.h>
int main(void)
{
FILE *fp = fopen("lines.txt", "w");
if (fp == NULL) {
printf("Could not open file for writing.\n");
return 1;
}
fputs("First line\n", fp);
fputs("Second line\n", fp);
fputs("Third line\n", fp);
fclose(fp);
/* Now read it back */
fp = fopen("lines.txt", "r");
if (fp == NULL) {
printf("Could not open file for reading.\n");
return 1;
}
char buffer[100];
while (fgets(buffer, sizeof(buffer), fp) != NULL) {
printf("Read: %s", buffer); /* buffer already has its own \n */
}
fclose(fp);
return 0;
}Read: First line Read: Second line Read: Third line
Checking for EOF Properly
fgets returns NULL when it hits end-of-file (or an error) before reading any characters, which is what the while loop above relies on to stop naturally. Functions like fscanf and fgetc instead signal end-of-file through their own return values — fscanf returns the number of items it successfully matched (or EOF on a read failure before any conversion), and fgetc returns the sentinel value EOF instead of a character code.
Function | End-of-file signal |
|---|---|
| Returns |
| Returns |
| Returns |
| Returns fewer items than requested |
Use
fprintf/fscanffor structured, formatted text dataUse
fgets/fputsfor whole-line text processing — the most common choice for reading text files safelyUse
fgetc/fputcwhen you need fine-grained, byte-by-byte control