Text files
A file in C is a FILE * from stdio.h, opened with a mode and closed before the program ends. Text mode writes and reads formatted lines, and the file functions reuse the printing and scanning of lesson 10 with a stream in place of the terminal. Every open can fail, and every result is checked.
Writing a text file
The mode "w" creates the file or empties an existing one. fprintf writes formatted text to the stream, and fclose flushes the buffer and returns the final status. The result is a normal text file with two lines.
#include <stdio.h>
int main(void)
{
FILE *file = fopen("notes.txt", "w");
if (file == NULL) {
perror("notes.txt");
return 1;
}
fprintf(file, "first line\n");
fprintf(file, "second line\n");
if (fclose(file) != 0) {
perror("fclose");
return 1;
}
printf("wrote two lines\n");
return 0;
}
gcc -Wall -Wextra write.c -o write
./write
cat notes.txt
wrote two lines
first line
second line
The program prints its own line and the shell prints the file. The file is created in the current directory and its size depends on the two strings; the newline is what makes cat and the next section's line reader see two lines.
Reading line by line
fgets reads at most one line into a buffer and returns NULL at the end of the file. The loop from lesson 10 works unchanged; the file is just another stream. The newline stays in the buffer, so the lines print exactly as they were written.
#include <stdio.h>
int main(void)
{
FILE *file = fopen("notes.txt", "r");
char line[64];
int number = 0;
if (file == NULL) {
perror("notes.txt");
return 1;
}
while (fgets(line, sizeof line, file) != NULL) {
number++;
printf("%d: %s", number, line);
}
fclose(file);
return 0;
}
gcc -Wall -Wextra read.c -o read
./read
1: first line
2: second line
The number comes from the loop, not from the file, and the format has no newline of its own because the line already ends with one. A line longer than 63 characters arrives in pieces, which is the limit of the buffer rather than of the file.
Appending
The mode "a" opens the file for writing at the end, creating it when it does not exist. Opening with "w" would have emptied the two lines from the first section. The program appends one line and then opens the same file for reading to prove the result.
#include <stdio.h>
int main(void)
{
FILE *file = fopen("notes.txt", "a");
if (file == NULL) {
perror("notes.txt");
return 1;
}
fprintf(file, "third line\n");
fclose(file);
file = fopen("notes.txt", "r");
if (file == NULL) {
perror("notes.txt");
return 1;
}
char line[64];
while (fgets(line, sizeof line, file) != NULL) {
printf("%s", line);
}
fclose(file);
return 0;
}
gcc -Wall -Wextra append.c -o append
./append
first line
second line
third line
The three lines exist because the first section created the file. Running only this program on a fresh directory would print one line, and running it twice would print the third line twice; append mode never truncates. The read after the write proves that the buffer was flushed by fclose.
When the open fails
A missing file is the normal failure of fopen, and it returns NULL with errno set. perror prints the program's label and the system message in one call; strerror gives the same text for a manual line.
#include <errno.h>
#include <stdio.h>
#include <string.h>
int main(void)
{
FILE *file = fopen("missing.txt", "r");
if (file == NULL) {
fprintf(stderr, "error %d: %s\n", errno, strerror(errno));
perror("missing.txt");
return 1;
}
fclose(file);
return 0;
}
gcc -Wall -Wextra errors.c -o errors
./errors; echo "status: $?"
error 2: No such file or directory
missing.txt: No such file or directory
status: 1
Both lines describe the same error: the number 2 is ENOENT and the text comes from the C library. The error output goes to standard error, which is why it appears before the status line when the two are captured together.
Truncation and a wrong format
The mode is a promise about the existing contents. Opening with "w" to read something and write it back empties the file before the first read, and the program below shows the result:
#include <stdio.h>
int main(void)
{
FILE *file = fopen("notes.txt", "w");
if (file == NULL) {
return 1;
}
fclose(file);
printf("file is now empty\n");
return 0;
}
gcc -Wall -Wextra truncate.c -o truncate
./truncate
wc -l notes.txt
file is now empty
0 notes.txt
The file still exists, with zero lines. A program that edits a file should read it completely, write a new temporary file, and rename it, rather than opening the original with "w". The second mistake is a format that does not match the argument, which fprintf checks exactly like printf:
#include <stdio.h>
int main(void)
{
FILE *file = fopen("out.txt", "w");
if (file == NULL) {
return 1;
}
fprintf(file, "value: %s\n", 42);
fclose(file);
return 0;
}
gcc -Wall -Wextra badformat.c -o badformat
badformat.c: In function 'main':
badformat.c:10:28: warning: format '%s' expects argument of type 'char *', but argument 3 has type 'int' [-Wformat=]
10 | fprintf(file, "value: %s\n", 42);
| ~^ ~~
| | |
| | int
| char *
| %d
The program is not run here: with %s the library reads the integer 42 as an address and tries to print the string there, which is a crash at best. The warning names the conversion that should have been used, %d, before the file is touched.
Exercises
- Write a program that counts the lines, words and bytes of a file opened from the command line, with the count passed in argc/argv.
- Copy a file line by line into a second file, and stop with an error when either open fails.
- Read a file of numbers, one per line, with fgets and strtol, and print the sum and the number of invalid lines.
Questions
A new set of questions in a new order every time. Choose one answer for each, then check.
-
What does the mode
"w"do to an existing file?Answer
It empties the file before writing
Write mode truncates the file, which is why editing in place needs a temporary file and a rename.
-
What does
fgetskeep in the buffer?Answer
The newline, when there is room for it
The newline stays in the buffer, so a plain
printfof the line reproduces the original break. -
What does
fclosedo besides releasing the stream?Answer
It flushes buffered output and reports errors
The buffered bytes are written out, and the return value reports whether the final write succeeded.
-
What does
perrorprint?Answer
A label and the system message for
errnoperrorcombines the caller's label with the text for the current error number, in one call. -
What does this program print?
#include <stdio.h> int main(void){FILE*f=fopen("/tmp/notes.txt","w");if(!f)return 1;fprintf(f,"first line\n");fprintf(f,"second line\n");fclose(f);printf("wrote two lines\n");return 0;}Answer
wrote two lines
The program prints its own message after closing the stream.
-
What does this program print for the two-line file?
#include <stdio.h> int main(void){FILE*f=fopen("/tmp/notes.txt","w");fprintf(f,"first line\nsecond line\n");fclose(f);f=fopen("/tmp/notes.txt","r");char l[64];int n=0;while(fgets(l,sizeof l,f)){n++;printf("%d: %s",n,l);}fclose(f);return 0;}Answer
1: first line / 2: second line
The loop numbers each line from one and prints the stored newline, so each entry ends cleanly.
-
What does this program print?
#include <stdio.h> int main(void){FILE*f=fopen("/tmp/notes.txt","w");fprintf(f,"first line\nsecond line\n");fclose(f);f=fopen("/tmp/notes.txt","a");fprintf(f,"third line\n");fclose(f);f=fopen("/tmp/notes.txt","r");char l[64];while(fgets(l,sizeof l,f))printf("%s",l);fclose(f);return 0;}Answer
first line / second line / third line
Append mode keeps the existing text, and the new line goes after the second one.
-
What does this program print?
#include <errno.h> #include <stdio.h> #include <string.h> int main(void){FILE*f=fopen("missing-file.txt","r");if(f==NULL){printf("error %d: %s\n",errno,strerror(errno));return 1;}fclose(f);return 0;}Answer
error 2: No such file or directory
ENOENTis 2, andstrerrorprints the system message for it. -
Why is opening a file with
"w"before reading it a mistake?Answer
The file is truncated before the read
Write mode empties the file immediately, so the read finds nothing; the temporary-file pattern avoids this.
-
What does GCC warn about in
fprintf(file, "value: %s\n", 42)?Answer
That
%sexpects achar *, not anintThe format is checked like
printf, and the warning names%das the conversion for the integer argument.
The code on this page was run with gcc (Alpine 15.2.0) 15.2.0 on Alpine Linux 3.24.3 (x86_64) in a Docker container.
Article author: Arthur Isaev