013 Project 13: Build a wc Clone
013 Build a wc Clone
Count lines, words, and bytes across files.
file -> scanner/read -> count L/W/B -> per file + total
Full main.go
package main
import (
"bufio"
"flag"
"fmt"
"io"
"os"
"unicode"
)
type counts struct {
lines int
words int
bytes int
}
func countReader(r io.Reader) (counts, error) {
var c counts
s := bufio.NewScanner(r)
for s.Scan() {
line := s.Text()
c.lines++
c.bytes += len(line) + 1
inWord := false
for _, ch := range line {
if unicode.IsSpace(ch) {
inWord = false
continue
}
if !inWord {
c.words++
inWord = true
}
}
}
return c, s.Err()
}
func main() {
flag.Parse()
if flag.NArg() == 0 {
c, err := countReader(os.Stdin)
if err != nil {
fmt.Fprintln(os.Stderr, err)
os.Exit(1)
}
fmt.Printf("%8d %8d %8d\n", c.lines, c.words, c.bytes)
return
}
var total counts
for _, path := range flag.Args() {
f, err := os.Open(path)
if err != nil {
fmt.Fprintf(os.Stderr, "%s: %v\n", path, err)
continue
}
c, err := countReader(f)
_ = f.Close()
if err != nil {
fmt.Fprintf(os.Stderr, "%s: %v\n", path, err)
continue
}
total.lines += c.lines
total.words += c.words
total.bytes += c.bytes
fmt.Printf("%8d %8d %8d %s\n", c.lines, c.words, c.bytes, path)
}
if flag.NArg() > 1 {
fmt.Printf("%8d %8d %8d total\n", total.lines, total.words, total.bytes)
}
}Run
go run . README.md
go run . *.qmdStep-by-Step Explanation
- Parse command-line flags and validate inputs early.
- Keep the core operation in a small, testable function.
- Process data as a stream when possible to reduce memory use.
- Print stable output and meaningful exit codes.
- Add one extension feature and test edge cases.
Code Anatomy
mainhandles flags, orchestration, and errors.- Worker/helper functions hold business logic.
- Output section should be deterministic for scripting and CI usage.
Learning Goals
- Write composable Unix-style Go tools.
- Improve error messages and operator experience.
- Practice iterative improvement over one clear baseline.