CReading & Writing Files

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.

C
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.

C
#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

fgets

Returns NULL

fgetc

Returns EOF (an int, not a char)

fscanf

Returns EOF, or fewer successful conversions than requested

fread

Returns fewer items than requested

Note
Reaching end-of-file is not itself an error — it is the normal, expected way a read loop terminates. The next pages (Binary Files and File Error Handling) dig into `feof()` and `ferror()`, which let you tell a clean end-of-file apart from an actual read failure.
  • Use fprintf/fscanf for structured, formatted text data

  • Use fgets/fputs for whole-line text processing — the most common choice for reading text files safely

  • Use fgetc/fputc when you need fine-grained, byte-by-byte control