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.txt

Step-by-Step Explanation

  1. Parse command-line flags and validate inputs early.
  2. Keep the core operation in a small, testable function.
  3. Process data as a stream when possible to reduce memory use.
  4. Print stable output and meaningful exit codes.
  5. Add one extension feature and test edge cases.

Code Anatomy

  • main handles 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.