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 Learn Go the Hard Way
Register today for the course and get the all currently available videos and lessons, plus all future modules for no extra charge.