/commands/c/read-file-line-by-line
c read a file line by line with fgets (and getline)
- tool
- C
- versions
- C89+ (fgets), getline: POSIX.1-2008
- os
- any
To read a text file line by line in C, open it with fopen(path, "r") and call fgets(buf, sizeof buf, fp) in a loop until it returns NULL. Each call reads one line, including its \n, up to the size of the buffer. On Linux and macOS, POSIX getline(&line, &cap, fp) does the same with a buffer that grows automatically. Always check fopen for NULL and fclose the file at the end.
Read with fgets
The example uses Clang 20 from the ziglang package on PyPI, run through uv; cc below stands for it. Write gcc or clang on your machine:
cc() { uv run -q --no-project --python 3.13 --with ziglang==0.15.1 python -m ziglang cc -fno-color-diagnostics "$@"; }
printf 'first line\nsecond line\n\nlast line without newline' > input.txt
cat > lines.c <<'EOF'
#include <stdio.h>
#include <string.h>
int main(void) {
FILE *fp = fopen("input.txt", "r");
if (fp == NULL) {
perror("input.txt");
return 1;
}
char line[256];
int n = 0;
while (fgets(line, sizeof line, fp) != NULL) {
line[strcspn(line, "\r\n")] = '\0'; /* strip the line ending */
printf("%d: [%s] (%zu chars)\n", ++n, line, strlen(line));
}
if (ferror(fp)) perror("read error");
fclose(fp);
fflush(stdout);
if (fopen("missing.txt", "r") == NULL) perror("missing.txt");
return 0;
}
EOF
cc -std=c17 -Wall -Wextra lines.c -o lines && ./lines 2>&1Output
1: [first line] (10 chars) 2: [second line] (11 chars) 3: [] (0 chars) 4: [last line without newline] (25 chars) missing.txt: No such file or directory
strcspn(line, "\r\n") finds the first \r or \n and cuts the line there, which handles Unix and Windows line endings and a last line without a newline. perror prints the reason a file could not be opened, on standard error; the fflush(stdout) keeps the two streams in order in the output.
With getline (POSIX)
char *line = NULL;
size_t cap = 0;
ssize_t len;
while ((len = getline(&line, &cap, fp)) != -1) {
/* line holds the whole line, including '\n'; len is its length */
}
free(line);getline allocates and grows the buffer as needed, so lines of any length are read in one piece. Free the buffer once at the end, not inside the loop.
Functions
| Function | Availability | Notes |
|---|---|---|
| fgets(buf, size, fp) | Standard C | Fixed buffer; keeps the newline; long lines arrive in pieces |
| getline(&line, &cap, fp) | POSIX (Linux, macOS, BSD) | Buffer grows; returns the length, -1 at end of file |
| getc / fgetc in a loop | Standard C | Character by character, full control, more code |
| fscanf(fp, "%255s", buf) | Standard C | Reads words, not lines; avoid for line-based input |
| gets(buf) | Removed in C11 | No size limit, never use it |
Pitfalls
- Not checking fopen. Using a
NULLFILE *crashes with a segmentation fault; check the result and printperror. while (!feof(fp)).feofonly becomes true after a read has already failed, so this loop processes the last line twice or works on stale data. Loop on the return value offgetsinstead.- Buffer too small. With a fixed buffer, test whether the line ends with
\n; if not, the line was longer than the buffer. - Text vs binary mode. On Windows,
"r"converts\r\nto\n;"rb"does not. Stripping both characters, as above, works either way. - Numbers in lines need parsing: see convert a string to an int with strtol.
Confirm it worked
Count the lines the program prints and compare with wc -l input.txt (which counts newline characters, so it reports one less when the last line has no newline, as in this example).
FAQ
Does fgets keep the newline?
Yes. fgets stores the \n in the buffer when the whole line fits. Remove it with line[strcspn(line, "\n")] = '\0', which also works when there is no newline.
What happens when a line is longer than the buffer?
fgets stops after size - 1 characters, and the rest of the line comes back in the next call. A buffer without a \n at the end means the line was cut; getline avoids the problem by growing the buffer.
Is getline available on Windows?
Not in the Microsoft C runtime. getline is a POSIX function, available on Linux, macOS and the BSDs; on Windows use fgets, or a library that provides it.