Skip to content

/commands/c/read-file-line-by-line

c read a file line by line with fgets (and getline)

verified onenvironmentlocal · windows 10.0.26200 arm64 · GNU bash 5.3.15 (Git Bash)updated
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:

bash
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>&1

Output

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)

c
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

FunctionAvailabilityNotes
fgets(buf, size, fp)Standard CFixed 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 loopStandard CCharacter by character, full control, more code
fscanf(fp, "%255s", buf)Standard CReads words, not lines; avoid for line-based input
gets(buf)Removed in C11No size limit, never use it

Pitfalls

  • Not checking fopen. Using a NULL FILE * crashes with a segmentation fault; check the result and print perror.
  • while (!feof(fp)). feof only 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 of fgets instead.
  • 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\n to \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.

Sources