Skip to content
AgileLogics
7 min read

File I/O

Prerequisites: Working with JSON

The os package handles files. For small files, read or write everything in one call; for large files or line-by-line processing, open the file and stream it.

  • os.ReadFile(path) returns the whole file as []byte.
  • os.WriteFile(path, data, perm) creates or truncates a file and writes data. perm is a Unix permission such as 0o644 (ignored for the most part on Windows).
  • os.Open(path) opens a file for reading; os.Create(path) creates or truncates one for writing; os.OpenFile gives full control (append, create, and so on).
  • Wrap an open file in a bufio.Scanner to read it line by line.
  • Always close files you open — defer f.Close() right after checking the error.

Syntax

data, err := os.ReadFile("notes.txt")
err = os.WriteFile("notes.txt", []byte("hello\n"), 0o644)

f, err := os.Open("notes.txt")
if err != nil {
    return err
}
defer f.Close()

scanner := bufio.NewScanner(f)
for scanner.Scan() {
    line := scanner.Text()
}
err = scanner.Err()

Example

package main

import (
    "bufio"
    "errors"
    "fmt"
    "log"
    "os"
    "strings"
)

func main() {
    path := "notes.txt"
    defer os.Remove(path) // clean up when main returns

    // Write the whole file at once.
    content := "first line\nsecond line\nthird line\n"
    if err := os.WriteFile(path, []byte(content), 0o644); err != nil {
        log.Fatal(err)
    }

    // Read the whole file at once.
    data, err := os.ReadFile(path)
    if err != nil {
        log.Fatal(err)
    }
    fmt.Printf("read %d bytes\n", len(data))

    // Append a line.
    af, err := os.OpenFile(path, os.O_APPEND|os.O_WRONLY, 0o644)
    if err != nil {
        log.Fatal(err)
    }
    fmt.Fprintln(af, "fourth line")
    if err := af.Close(); err != nil { // a failed Close can mean lost data
        log.Fatal(err)
    }

    // Stream it line by line.
    f, err := os.Open(path)
    if err != nil {
        log.Fatal(err)
    }
    defer f.Close()

    scanner := bufio.NewScanner(f)
    lineNo := 0
    for scanner.Scan() {
        lineNo++
        fmt.Printf("%d: %s\n", lineNo, strings.ToUpper(scanner.Text()))
    }
    if err := scanner.Err(); err != nil {
        log.Fatal(err)
    }

    // Detect a missing file.
    _, err = os.ReadFile("missing.txt")
    fmt.Println(errors.Is(err, os.ErrNotExist))
}

Expected output

read 34 bytes
1: FIRST LINE
2: SECOND LINE
3: THIRD LINE
4: FOURTH LINE
true

Deferred calls run in reverse order, so f.Close() runs before os.Remove(path) — which matters on Windows, where an open file cannot be deleted.

Common mistakes

  • Forgetting scanner.Err() after the loop. Scan returns false both at the end of the file and on an error.
  • Very long lines: a bufio.Scanner has a 64 KB line limit by default and stops with bufio.Scanner: token too long. Raise it with scanner.Buffer(...) or use bufio.Reader.
  • Ignoring the error from Close on a file you wrote. Buffered data is flushed on close, so that error can mean the write failed.
  • Relative paths are resolved against the current working directory, not the location of your .go file.
  • defer f.Close() inside a loop that opens many files keeps every file open until the function returns.

Practice

Write the numbers 10, 20 and 30 to nums.txt, one per line. Then open the file, read it back line by line with a bufio.Scanner, convert each line with strconv.Atoi, and print the sum.

Show solution
package main

import (
    "bufio"
    "fmt"
    "log"
    "os"
    "strconv"
)

func main() {
    if err := os.WriteFile("nums.txt", []byte("10\n20\n30\n"), 0o644); err != nil {
        log.Fatal(err)
    }

    f, err := os.Open("nums.txt")
    if err != nil {
        log.Fatal(err)
    }
    defer f.Close()

    sum := 0
    scanner := bufio.NewScanner(f)
    for scanner.Scan() {
        n, err := strconv.Atoi(scanner.Text())
        if err != nil {
            log.Fatal(err)
        }
        sum += n
    }
    if err := scanner.Err(); err != nil {
        log.Fatal(err)
    }
    fmt.Println("sum:", sum) // sum: 60
}