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" *.logStep-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.