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.
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])
}
}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.
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.