008 Project 8: Build a cat Clone
008 Build a cat Clone
Create a streaming file printer with line numbers and optional end-of-line markers.
stdin/files -> scanner -> transform lines -> stdout
Full main.go
package main
import (
"bufio"
"flag"
"fmt"
"io"
"os"
)
func cat(r io.Reader, number, showEnds bool, start int) (int, error) {
s := bufio.NewScanner(r)
lineNo := start
for s.Scan() {
line := s.Text()
if showEnds {
line += "$"
}
if number {
fmt.Printf("%6d\t%s\n", lineNo, line)
lineNo++
} else {
fmt.Println(line)
}
}
return lineNo, s.Err()
}
func main() {
number := flag.Bool("n", false, "number output lines")
showEnds := flag.Bool("E", false, "show $ at end of line")
flag.Parse()
lineNo := 1
if flag.NArg() == 0 {
if _, err := cat(os.Stdin, *number, *showEnds, lineNo); err != nil {
fmt.Fprintln(os.Stderr, err)
os.Exit(1)
}
return
}
for _, path := range flag.Args() {
f, err := os.Open(path)
if err != nil {
fmt.Fprintf(os.Stderr, "%s: %v\n", path, err)
continue
}
var n int
n, err = cat(f, *number, *showEnds, lineNo)
_ = f.Close()
if err != nil {
fmt.Fprintf(os.Stderr, "%s: %v\n", path, err)
}
lineNo = n
}
}Run
go run . file.txt
go run . -n -E file1.txt file2.txtStep-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.