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

C
#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;
}
Output
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

ModeMeaningIf the file existsIf it doesn't
"r"ReadRead from the startFails (NULL)
"w"WriteErased, then writtenCreated
"a"AppendWrites go to the endCreated
"r+", "w+", "a+"Read and writeAs above, but both directions allowed
"wx" (C11)Write a new fileFails — protects existing filesCreated

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:

TXT inventory.csv
apples,30,0.45
bananas,24,0.30
cherries,200,0.05
dates,15,0.80
C
#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;
}
Output
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

C
#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;
}
Output
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:

TXT scores.txt
Ana 82
Ben 91
Chloe 77
C
#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;
}
Output
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:

C
#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;
}
Output
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.

TXT poem.txt
Roses are red,
pointers are tricky,
free what you malloc
or your memory gets sticky.
Try it yourself · main.c
Edit the code, copy it into main.c, then compile and run it with cc -std=c17 -Wall main.c -o main && ./main. How to set up a C compiler
Output
  1  Roses are red,
  2  pointers are tricky,
  3  free what you malloc
  4  or your memory gets sticky.