WeCodeAcademy

Step 6: Read from a file like a real tool

A real command-line tool reads its input from a file or from standard input, not from a string in the code. This Go version writes a small file, then reads it line by line with a bufio.Scanner, counting words with strings.Fields, and reports the number of lines, the distinct words and the top three. Reading line by line means the tool works on files far bigger than memory.

ExampleGo
package main

import (
  "bufio"
  "fmt"
  "os"
  "path/filepath"
  "sort"
  "strings"
)

func main() {
  // write a small file, then read it line by line the way a real tool reads its input
  path := filepath.Join(os.TempDir(), "wordcount-demo.txt")
  os.WriteFile(path, []byte("the cat sat\non the mat\nthe end\n"), 0644)
  defer os.Remove(path)

  f, err := os.Open(path)
  if err != nil {
    fmt.Println("error:", err)
    return
  }
  defer f.Close()

  counts := map[string]int{}
  lines := 0
  scanner := bufio.NewScanner(f)
  for scanner.Scan() {
    lines++
    for _, w := range strings.Fields(scanner.Text()) {
      counts[w]++
    }
  }

  words := make([]string, 0, len(counts))
  for w := range counts {
    words = append(words, w)
  }
  sort.Slice(words, func(i, j int) bool {
    if counts[words[i]] != counts[words[j]] {
      return counts[words[i]] > counts[words[j]]
    }
    return words[i] < words[j]
  })
  fmt.Println("lines:", lines, "distinct words:", len(words))
  for _, w := range words[:3] {
    fmt.Printf("%-4s %d\n", w, counts[w])
  }
}
Output
lines: 3 distinct words: 6
the  3
cat  1
end  1

Notice the defer calls: defer f.Close() and defer os.Remove(path) guarantee cleanup however the function ends, and the error from os.Open is checked immediately, the standard Go style.

Quick check

Why read a file line by line instead of all at once?

Finish the quick check (and any code exercise) to complete this lesson, or mark it done yourself.

Tip: use the left and right arrow keys to move between lessons.