009 Project 9: Build a grep Clone

009 Build a grep Clone

Search text with regex, case-insensitive mode, and line numbers.

files -> scan line by line -> regex match -> print matches

Full main.go

package main

import (
    "bufio"
    "flag"
    "fmt"
    "os"
    "regexp"
)

func grepFile(path string, re *regexp.Regexp, showLineNo bool) error {
    f, err := os.Open(path)
    if err != nil {
        return err
    }
    defer f.Close()

    s := bufio.NewScanner(f)
    lineNo := 0
    for s.Scan() {
        lineNo++
        line := s.Text()
        if re.MatchString(line) {
            if showLineNo {
                fmt.Printf("%s:%d:%s\n", path, lineNo, line)
            } else {
                fmt.Printf("%s:%s\n", path, line)
            }
        }
    }
    return s.Err()
}

func main() {
    ignoreCase := flag.Bool("i", false, "ignore case")
    showLineNo := flag.Bool("n", false, "show line number")
    flag.Parse()

    if flag.NArg() < 2 {
        fmt.Fprintln(os.Stderr, "usage: ggrep [flags] <pattern> <file...>")
        os.Exit(2)
    }

    pattern := flag.Arg(0)
    if *ignoreCase {
        pattern = "(?i)" + pattern
    }
    re, err := regexp.Compile(pattern)
    if err != nil {
        fmt.Fprintln(os.Stderr, "invalid regex:", err)
        os.Exit(2)
    }

    for _, file := range flag.Args()[1:] {
        if err := grepFile(file, re, *showLineNo); err != nil {
            fmt.Fprintf(os.Stderr, "%s: %v\n", file, err)
        }
    }
}

Run

go run . error app.log
go run . -i -n "timeout|refused" *.log

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.