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.
- 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.
Opening and closing files
Open, close, and check the status of files in C using fopen, fclose, and the FILE type — including mode strings and error handling.
Binary file I/O
Read and write raw binary data in C using fread, fwrite, fseek, and ftell — including struct serialisation and portability considerations.