Video Coming Soon...
45: grep
The grep command is an extremely useful tool. It searches through files--even recursively--looking for patterns of text. These patterns use something called a "regular expression", or "regex" for short. A regex uses simple characters to create a matching pattern that can search for things like, "all numbers starting with 0 through 9 before a lowercase letter."
Here's a quick regex crash course:
.-- means "any 1 character"*-- means "zero or more of the previous"+-- means "one or more of the previous"[a-z]-- means "any character in the set of letters a-z." You can put any range here so[0-9]is numbers and[0-9a-z]is both.\\-- means "ignore the next character, it's not regex." So\.means, "ignore . it's not regex."
After that you can put any text you want and those letters get matched exactly. Here's some regex to try on your own code:
func [A-Z][a-zA-Z]*-- Find all exported functions. Read this as, "func followed by any capital letter then any number of lower or upper case letters."[a-zA-Z]+ :=-- Find all assignments. Read this as "any sequence of characters followed by a space and :=."fmt\.-- Every line that usesfmt.
The Challenge
Your grep should take a single regex and scan either a listed number of files or the os.Stdin input stream. It should also be able to process the three example regex I gave you and run on your main.go file.
The documentation for grep is:
Requirements
The only package that is mandatory for grep is the regexp package. Here's what I used:
See the list of requirements
- fmt -- https://pkg.go.dev/fmt
- regexp -- https://pkg.go.dev/regexp
- os -- https://pkg.go.dev/os
- bufio -- https://pkg.go.dev/bufio
- flag -- https://pkg.go.dev/flag
- log -- https://pkg.go.dev/log
- io -- https://pkg.go.dev/io
Spoilers
Here's my quick implementation of grep to get you going. Can you do better?
See my first version code
View Source file go-coreutils/grep/main.go Onlypackage main
import (
"fmt"
"regexp"
"os"
"bufio"
"flag"
"log"
"io"
)
func ScanInput(input io.Reader, exp string, prefix string) {
scan := bufio.NewScanner(input)
re, err := regexp.Compile(exp)
if err != nil { log.Fatal(err) }
for scan.Scan() {
line := scan.Text()
if re.MatchString(line) {
if prefix != "" {
fmt.Print(prefix, ": ")
}
fmt.Println(line)
}
}
}
func main() {
// NOTE: can we avoid flag here since it's just raw args?
flag.Parse()
args := flag.Args()
if len(args) == 0 {
log.Fatal("USAGE: grep <regex> [files...]")
} else if len(args) == 1 {
ScanInput(os.Stdin, args[0], "")
} else {
exp := args[0]
files := args[1:]
for _, file := range files {
input, err := os.Open(file)
if err != nil { log.Fatal(err) }
ScanInput(input, exp, file)
}
}
}
Testing It
If you want to push your learning further then you can try to implement an automated test for this. I actually need to learn how to test utilities like this with Go, so for now just consider this an extra challenge for later until I learn how to teach it.
Register for The Pro-Webdev Mega Bundle
Register today for the course and get the all currently available videos and lessons, plus all future modules for no extra charge.