Code of the Day
IntermediateFile I/O

Reading and writing text files

Read and write line-oriented text files in C using fgets, fputs, fprintf, and fscanf — with examples for line processing and formatted output.

CIntermediate11 min read
Recommended first
By the end of this lesson you will be able to:
  • Write formatted output to a file with fprintf
  • Read lines from a file with fgets
  • Use fscanf to parse structured text from a file
  • Detect end-of-file with feof and distinguish it from a read error

Once you have an open FILE *, you interact with it through read/write functions. The text-mode functions — fprintf, fgets, fputs, fscanf — work with character data and newlines. They are the right choice for configuration files, log files, CSVs, and any data format humans can read.

Writing with fprintf and fputs

fprintf works exactly like printf but writes to a FILE:

#include <stdio.h>

int main(void) {
    FILE *fp = fopen("log.txt", "w");
    if (!fp) { perror("fopen"); return 1; }

    fprintf(fp, "Entry 1: score=%d\n", 95);
    fprintf(fp, "Entry 2: score=%d\n", 87);

    fclose(fp);
    return 0;
}

fputs(str, fp) writes a string without format processing — faster when you do not need formatting:

fputs("line one\n", fp);
fputs("line two\n", fp);

Reading lines with fgets

fgets(buf, size, fp) reads at most size - 1 characters from fp into buf, stopping at a newline (which it includes in the buffer) or EOF. It always null-terminates.

#include <stdio.h>

int main(void) {
    FILE *fp = fopen("log.txt", "r");
    if (!fp) { perror("fopen"); return 1; }

    char line[256];
    while (fgets(line, sizeof(line), fp) != NULL) {
        printf("Read: %s", line); /* line already contains \n */
    }

    fclose(fp);
    return 0;
}

fgets returns NULL on error or end-of-file. The loop above stops at either.

Stripping the trailing newline:

size_t len = strlen(line);
if (len > 0 && line[len - 1] == '\n') {
    line[len - 1] = '\0';
}

Reading structured data with fscanf

fscanf(fp, format, ...) reads formatted input from a file, analogous to scanf from stdin:

/* numbers.txt contains: 10 20 30 40 50 */
FILE *fp = fopen("numbers.txt", "r");
if (!fp) { perror("fopen"); return 1; }

int n;
while (fscanf(fp, "%d", &n) == 1) {
    printf("Read: %d\n", n);
}

fclose(fp);

fscanf returns the number of items successfully read. When it hits EOF or a format mismatch, it returns fewer than expected (or EOF = -1). Always check the return value.

fscanf("%s", buf) is dangerous. It reads whitespace-delimited tokens but has no length limit. Use fscanf("%255s", buf) (with the buffer size minus 1) to avoid buffer overflows from untrusted input. For line-by-line processing, prefer fgets followed by sscanf.

Detecting EOF vs. error

if (feof(fp)) {
    /* reached end of file -- normal */
} else if (ferror(fp)) {
    perror("read error");
}

feof(fp) is true after a read returns NULL or EOF because the end was reached. ferror(fp) is true if an I/O error occurred. Check both after a read failure to distinguish normal termination from a problem.

A complete read-process-write pipeline

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

int main(void) {
    FILE *in  = fopen("input.txt", "r");
    FILE *out = fopen("output.txt", "w");

    if (!in || !out) { perror("fopen"); return 1; }

    char line[1024];
    int line_num = 0;
    while (fgets(line, sizeof(line), in)) {
        line_num++;
        /* Strip trailing newline */
        size_t len = strlen(line);
        if (len > 0 && line[len-1] == '\n') { line[len-1] = '\0'; }

        fprintf(out, "%4d: %s\n", line_num, line);
    }

    printf("Processed %d lines\n", line_num);

    fclose(in);
    fclose(out);
    return 0;
}

This reads each line, strips the newline, and writes a numbered version to the output file.

Where to go next

Next: binary file I/O — reading and writing raw bytes with fread and fwrite, for binary formats, image files, and serialised data structures.

Finished reading? Mark it complete to track your progress.

On this page