Reading and Writing Text Files in C

When your program ends, all its variables disappear. Everything in memory is gone.

If you want to save data - high scores, settings, documents - you need to write it to a file.

Files are collections of data stored on your hard drive or SSD. This lesson teaches you how to create files, write to them, and read them back.

Opening a File

Before you can read or write, you need to open the file:

#include <stdio.h>

int main(void) {
    FILE *file = fopen("data.txt", "r");  // Open for reading

    if (file == NULL) {
        printf("Couldn't open file!\n");
        return 1;
    }

    // Use the file...

    fclose(file);  // Always close when done
    return 0;
}

Let’s break this down:

  • FILE *file - Creates a variable that represents an open file. Think of it like a ticket that lets you access the file.
  • fopen("data.txt", "r") - Opens a file called “data.txt” for reading
  • fclose(file) - Closes the file when you’re done

Important: Always check if fopen returned NULL. This happens if the file doesn’t exist or you don’t have permission to open it.

File Modes

The second argument to fopen tells it what you want to do:

ModeWhat it does
"r"Read - Open an existing file to read it
"w"Write - Create a new file (or erase an existing one)
"a"Append - Add to the end of a file (creates if needed)
"r+"Read and write - File must already exist
"w+"Read and write - Creates new file (or erases existing)
"a+"Read and append - Creates if needed, writes go to end

Warning: "w" mode erases the file if it exists! Use "a" if you want to add to an existing file.

Writing to a File

Writing Text with fprintf

fprintf works just like printf, but writes to a file:

#include <stdio.h>

int main(void) {
    FILE *file = fopen("output.txt", "w");  // Open for writing

    if (file == NULL) {
        printf("Couldn't create file!\n");
        return 1;
    }

    fprintf(file, "Hello, File!\n");
    fprintf(file, "The answer is %d\n", 42);
    fprintf(file, "Pi is about %.2f\n", 3.14159);

    fclose(file);
    printf("Done! Check output.txt\n");
    return 0;
}

After running this, output.txt contains:

Hello, File!
The answer is 42
Pi is about 3.14

Writing Strings with fputs

For simple strings without formatting:

FILE *file = fopen("output.txt", "w");
fputs("Line 1\n", file);
fputs("Line 2\n", file);
fclose(file);

Reading from a File

Reading Line by Line with fgets

fgets reads one line at a time:

#include <stdio.h>

int main(void) {
    FILE *file = fopen("input.txt", "r");

    if (file == NULL) {
        printf("Couldn't open file!\n");
        return 1;
    }

    char line[256];  // Buffer to hold each line

    while (fgets(line, sizeof(line), file) != NULL) {
        printf("Read: %s", line);  // line already has \n
    }

    fclose(file);
    return 0;
}

fgets(line, sizeof(line), file) means:

  • Read into line
  • Read at most sizeof(line) - 1 characters (leaves room for the null terminator)
  • Read from file
  • Stop at a newline or end of file
  • Return NULL when there’s nothing left to read

Reading Formatted Data with fscanf

fscanf works like scanf, but reads from a file:

#include <stdio.h>

int main(void) {
    FILE *file = fopen("numbers.txt", "r");

    if (file == NULL) {
        return 1;
    }

    int num;
    while (fscanf(file, "%d", &num) == 1) {  // Keep reading while successful
        printf("Got: %d\n", num);
    }

    fclose(file);
    return 0;
}

If numbers.txt contains:

10
20
30

The program prints:

Got: 10
Got: 20
Got: 30

Error Messages with perror

When something goes wrong, perror prints a helpful message:

FILE *file = fopen("nonexistent.txt", "r");

if (file == NULL) {
    perror("Error opening file");
    // Prints: Error opening file: No such file or directory
    return 1;
}

perror adds the system’s error message, so you know exactly what went wrong.

A Complete Example: High Scores

Let’s build something practical - a program that saves and loads high scores:

#include <stdio.h>
#include <string.h>

#define MAX_SCORES 5

typedef struct {
    char name[20];
    int score;
} HighScore;

void save_scores(HighScore scores[], int count) {
    FILE *file = fopen("highscores.txt", "w");
    if (file == NULL) {
        printf("Couldn't save scores!\n");
        return;
    }

    for (int i = 0; i < count; i++) {
        fprintf(file, "%s %d\n", scores[i].name, scores[i].score);
    }

    fclose(file);
    printf("Scores saved!\n");
}

int load_scores(HighScore scores[], int max) {
    FILE *file = fopen("highscores.txt", "r");
    if (file == NULL) {
        return 0;  // No saved scores yet
    }

    int count = 0;
    while (count < max &&
           fscanf(file, "%19s %d", scores[count].name, &scores[count].score) == 2) {
        count++;
    }

    fclose(file);
    return count;
}

void print_scores(HighScore scores[], int count) {
    printf("\n=== HIGH SCORES ===\n");
    for (int i = 0; i < count; i++) {
        printf("%d. %-15s %d\n", i + 1, scores[i].name, scores[i].score);
    }
    printf("===================\n");
}

int main(void) {
    HighScore scores[MAX_SCORES];
    int count = load_scores(scores, MAX_SCORES);

    if (count > 0) {
        printf("Loaded %d saved scores\n", count);
        print_scores(scores, count);
    } else {
        printf("No saved scores found\n");
    }

    // Add a new score
    if (count < MAX_SCORES) {
        printf("\nEnter your name: ");
        scanf("%19s", scores[count].name);
        printf("Enter your score: ");
        scanf("%d", &scores[count].score);
        count++;

        save_scores(scores, count);
        print_scores(scores, count);
    }

    return 0;
}

Run it multiple times - your scores persist between runs!

Appending to Files

Use "a" mode to add to the end of a file without erasing it:

FILE *log = fopen("log.txt", "a");
fprintf(log, "Program started\n");
fclose(log);

Every time you run this, it adds another line to the log file.

Checking for End of File

The feof() function tells you if you’ve reached the end:

while (!feof(file)) {
    // Read stuff...
}

But it’s usually better to check the return value of your read function (like we did with fgets returning NULL).

Try It Yourself

  1. Write a program that counts how many lines are in a text file
  2. Write a program that copies one file to another
  3. Create a simple note-taking app that saves notes to a file and shows all saved notes
  4. Write a program that reads names from a file and prints them in reverse order

Common Mistakes

  • Forgetting to check if fopen returned NULL
  • Forgetting to call fclose when done (your data might not get saved!)
  • Using "r" mode when the file doesn’t exist
  • Using "w" mode when you wanted "a" (write erases, append adds)
  • Forgetting the & in fscanf for numbers (but not for strings!)

Next Up

In Part 22, we’ll learn about binary files - saving data exactly as it exists in memory, which is faster and more compact.


Enjoyed This?

If this helped something click, subscribe to my YouTube channel. More content like this, same approach - making things stick without insulting your intelligence. It’s free, it helps more people find this stuff, and it tells me what’s worth making more of.