xSKILLby Infirals
Register

C for Beginners

0 of 24 notes read 0%

Reading progress is saved to your INFIRALS account. Sign in to keep this note marked as read.

Reading and writing text files

Records, files and recursion13 min readLesson 4 of 6

4.1 A FILE handle

fopen(path, mode) asks the operating system for a stream and returns a FILE *. The pointer is a handle, and every later call uses it.

Modes: "r" reads an existing file, "w" creates or truncates, "a" appends, and adding b selects binary mode. Text mode on Windows translates \n to \r\n on write and back on read.

fopen returns NULL on failure: missing file, no permission, a bad path. Every program that opens a file checks the result before using the handle.

c
#include <stdio.h>

int main(void)
{
    FILE *out = fopen("greeting.txt", "w");
    if (out == NULL) {
        printf("could not open for writing\n");
        return 1;
    }
    fprintf(out, "hello file\n");
    fclose(out);
    printf("file written\n");
    return 0;
}

Output

file written

4.2 fprintf writes formatted text

fprintf(stream, format, ...) works like printf and sends the result to a stream. fputs(text, stream) writes a string without formatting, and fputc writes one character.

fprintf returns the number of characters written, or a negative value on error. Checking every write is tedious, so check ferror(stream) before closing.

Buffering means the bytes may not reach the disk immediately. fflush(stream) forces the buffer out, and fclose flushes as part of closing.

c
#include <stdio.h>

int main(void)
{
    FILE *log = fopen("run.log", "w");
    if (log == NULL) return 1;
    for (int i = 1; i <= 3; i++) {
        fprintf(log, "step %d complete\n", i);
    }
    if (ferror(log)) printf("write failed\n");
    fclose(log);
    printf("log closed\n");
    return 0;
}

Output

log closed

4.3 fgets reads a line at a time

fgets(buffer, size, stream) returns the buffer on success and NULL at end of file or on error. It keeps the newline, so strip it before comparing with other text.

A loop that reads until NULL handles files with or without a final newline. The buffer size caps one line, and a longer line arrives in pieces, which the missing newline reveals.

fgetc returns one character as an int, or EOF at the end. Keep the result in an int and compare it with EOF, because a char cannot hold every value the function may return.

c
#include <stdio.h>
#include <string.h>
int main(void)
{
    FILE *out = fopen("notes.txt", "w");
    if (out == NULL) return 1;
    fputs("first line\nsecond line\nthird line\n", out);
    fclose(out);
    FILE *in = fopen("notes.txt", "r");
    if (in == NULL) return 1;
    char line[32];
    int number = 0;
    while (fgets(line, sizeof line, in) != NULL) {
        line[strcspn(line, "\r\n")] = '\0';
        printf("%d: %s\n", ++number, line);
    }
    fclose(in);
    return 0;
}

Output

1: first line
2: second line
3: third line

4.4 The open, use, close pattern

Every successful fopen needs a matching fclose. Closing releases the buffer and the operating system handle, and it reports errors that happened during buffered writes.

The clean shape is: open, check, work, close, return. Early returns between the open and the close leak handles, so initialise every FILE * to NULL and close it only when it is not null.

When several files are open, a single cleanup label handles every exit path. The heap cleanup pattern from the previous module applies here unchanged.

c
#include <stdio.h>

int main(void)
{
    int status = 1;
    FILE *a = fopen("a.txt", "w");
    FILE *b = NULL;
    if (a != NULL) b = fopen("b.txt", "w");
    if (a != NULL && b != NULL) {
        fprintf(a, "3\n");
        fprintf(b, "4\n");
        status = 0;
    }
    if (b != NULL) fclose(b);
    if (a != NULL) fclose(a);
    printf("status: %d\n", status);
    return 0;
}

Output

status: 0

4.5 Failing to open

A missing file is not an error in the language, it is a result. fopen returns NULL, and errno holds a code that describes why.

perror("open") prints that code in readable form after a label. The exact text depends on the platform and the locale, so a program that needs a stable message should print its own.

Returning a nonzero status from main lets scripts detect the failure. A diagnostic belongs on stderr, which keeps it out of piped output.

c
#include <stdio.h>

int main(void)
{
    FILE *in = fopen("missing.txt", "r");
    if (in == NULL) {
        printf("could not open missing.txt\n");
        return 1;
    }
    fclose(in);
    printf("opened\n");
    return 0;
}

Output

could not open missing.txt

4.6 Appending and counting

Opening with "a" writes at the end without truncating. That suits logs and files that accumulate records across runs.

Appending then reopening for reading shows that the data survived. Counting lines means reading each one and incrementing a counter, or counting newlines with fgetc.

Text mode translation means the byte count on disk can differ from the character count on Windows, where each newline becomes two bytes. Counting characters read in text mode gives the number the program sees.

c
#include <stdio.h>
int main(void)
{
    FILE *out = fopen("append.txt", "w");
    if (out == NULL) return 1;
    fputs("one\n", out);
    fclose(out);
    out = fopen("append.txt", "a");
    if (out == NULL) return 1;
    fputs("two\nthree\n", out);
    fclose(out);
    FILE *in = fopen("append.txt", "r");
    if (in == NULL) return 1;
    int lines = 0;
    char buffer[32];
    while (fgets(buffer, sizeof buffer, in) != NULL) lines++;
    fclose(in);
    printf("lines: %d\n", lines);
    return 0;
}

Output

lines: 3

4.7 One character at a time

fgetc reads one byte and returns it as an int, which leaves room for EOF. A loop that tests for EOF handles text and binary data the same way.

fread and fwrite move blocks and return counts of complete items. A short count means the file ended or an error occurred, and feof or ferror tells you which.

Binary mode with "rb" and "wb" turns off newline translation. Use it for anything that is not text, and use text mode for records you can read in an editor.

c
#include <stdio.h>

int main(void)
{
    FILE *out = fopen("count.txt", "w");
    if (out == NULL) return 1;
    fputs("count these\ntwo lines\n", out);
    fclose(out);

    FILE *in = fopen("count.txt", "r");
    if (in == NULL) return 1;
    int chars = 0, lines = 0, ch;
    while ((ch = fgetc(in)) != EOF) {
        chars++;
        if (ch == '\n') lines++;
    }
    fclose(in);
    printf("%d chars, %d lines\n", chars, lines);
    return 0;
}

Output

22 chars, 2 lines

Interview questions

3 questions from this note. Open one to see the answer; no account needed.

  • 01What does `fopen` return when the file cannot be opened?

    A null pointer. Check for `NULL` before passing the handle to any other file function.

  • 02Why does `fgets` keep the newline?

    It reads up to the size limit or through the newline, and it stores the character that ended the read. Removing it before comparisons is the caller's job.

  • 03What is the difference between `"w"` and `"a"`?

    `"w"` creates the file or truncates it to zero length. `"a"` creates it if needed and writes new data at the end.