添加基本功能
想要达到项目目标,需要实现以下功能:
-c, --count
-i, --ignore-case
-n, --line-number
-r, --recursive
-v, --invert-match
main.go:
package main
import (
"flag"
"fmt"
"log"
"os"
"github.com/Literank/gorep/pkg/grep"
)
func main() {
// 设置自定义使用方法信息
flag.Usage = func() {
fmt.Fprintf(os.Stderr, "Usage: %s [options] pattern file_path\n", os.Args[0])
fmt.Println("Options:")
flag.PrintDefaults()
}
// 可选参数
countFlag := flag.Bool("c", false, "count\nOnly a count of selected lines is written to standard output.")
ignoreCaseFlag := flag.Bool("i", false, "ignore-case\nPerform case insensitive matching. By default, it is case sensitive.")
lineNumberFlag := flag.Bool("n", false, "line-number\nEach output line is preceded by its relative line number in the file, starting at line 1. This option is ignored if -count is specified.")
recursiveFlag := flag.Bool("r", false, "recursive\nRecursively search subdirectories listed.")
invertMatchFlag := flag.Bool("v", false, "invert-match\nSelected lines are those not matching any of the specified patterns.")
flag.Parse()
// 获取位置参数
// pattern - 要搜索匹配的模式
// file_path - 要搜索匹配的文件路径
args := flag.Args()
if len(args) < 2 {
fmt.Println("Both pattern and file_path are required.")
flag.Usage()
os.Exit(0)
}
pattern, filePath := args[0], args[1]
options := &grep.Options{}
if *ignoreCaseFlag {
options.IgnoreCase = true
}
if *invertMatchFlag {
options.InvertMatch = true
}
var result grep.MatchResult
var err error
if *recursiveFlag {
result, err = grep.GrepRecursive(pattern, filePath, options)
if err != nil {
log.Fatal("Failed to do recursive grep, error:", err)
}
} else {
result, err = grep.Grep(pattern, filePath, options)
if err != nil {
log.Fatal("Failed to grep, error:", err)
}
}
if *countFlag {
fmt.Println(grep.GrepCount(result))
} else {
printResult(result, *lineNumberFlag)
}
}
func printResult(result grep.MatchResult, lineNumberOption bool) {
currentFile := ""
fileCount := len(result)
for filePath, items := range result {
for _, item := range items {
if fileCount > 1 && filePath != currentFile {
currentFile = filePath
fmt.Printf("\n%s:\n", filePath)
}
if lineNumberOption {
fmt.Printf("%d: %s\n", item.LineNumber, item.Line)
} else {
fmt.Println(item.Line)
}
}
}
}
flag
加入所有可选参数。printResult
函数解析 MatchResult
并按需打印 filePath
和 lineNumber
。
pkg/grep/search.go:
package grep
import (
"bufio"
"os"
"path/filepath"
"regexp"
"strings"
)
// MatchItem 表示 grep 搜索的匹配项
type MatchItem struct {
LineNumber int
Line string
}
// MatchResult 表示 grep 搜索的所有文件的匹配结果
type MatchResult = map[string][]*MatchItem
// Options 结构体表示 grep 搜索的控制选项
type Options struct {
CountOnly bool
IgnoreCase bool
InvertMatch bool
}
func Grep(pattern string, filePath string, options *Options) (MatchResult, error) {
lines, err := readFileLines(filePath)
if err != nil {
return nil, err
}
var matchingLines []*MatchItem
patternRegex, err := regexp.Compile(pattern)
if err != nil {
return nil, err
}
// 常规 grep
if options == nil {
matchingLines = filterLines(patternRegex, lines, true)
} else {
if options.IgnoreCase {
patternRegex, err = regexp.Compile("(?i)" + pattern)
if err != nil {
return nil, err
}
}
if options.InvertMatch {
matchingLines = filterLines(patternRegex, lines, false)
} else {
matchingLines = filterLines(patternRegex, lines, true)
}
}
return MatchResult{filePath: matchingLines}, nil
}
func GrepCount(result MatchResult) int {
count := 0
for _, v := range result {
count += len(v)
}
return count
}
func GrepRecursive(pattern string, directoryPath string, options *Options) (MatchResult, error) {
results := make(MatchResult)
filepath.Walk(directoryPath, func(filePath string, info os.FileInfo, err error) error {
if err != nil {
return nil
}
if !info.IsDir() {
result, grepErr := Grep(pattern, filePath, options)
if grepErr != nil {
return grepErr
}
results[filePath] = result[filePath]
}
return nil
})
return results, nil
}
func readFileLines(filePath string) ([]string, error) {
// 打开文件
file, err := os.Open(filePath)
if err != nil {
return nil, err
}
defer file.Close()
// 创建 scanner 来按行读取
scanner := bufio.NewScanner(file)
var lines []string
for scanner.Scan() {
lines = append(lines, scanner.Text())
}
if err := scanner.Err(); err != nil {
return nil, err
}
return lines, nil
}
func filterLines(pattern *regexp.Regexp, lines []string, flag bool) []*MatchItem {
var filteredLines []*MatchItem
for lineNumber, line := range lines {
if flag == pattern.MatchString(line) {
filteredLines = append(filteredLines, &MatchItem{lineNumber + 1, strings.TrimLeft(line, " \t")})
}
}
return filteredLines
}
Grep
函数添加“不区分大小写匹配”和“反向匹配”逻辑。GrepRecursive
函数递归地列出所有文件,执行 grep
操作,然后输出到 MatchResult
中。
常规用法:
go run main.go result main.go
结果:
var result grep.MatchResult
result, err = grep.GrepRecursive(pattern, filePath, options)
result, err = grep.Grep(pattern, filePath, options)
fmt.Println(grep.GrepCount(result))
printResult(result, *lineNumberFlag)
func printResult(result grep.MatchResult, lineNumberOption bool) {
fileCount := len(result)
for filePath, items := range result {
计数:
go run main.go -c result main.go
结果数字:8。
显示行号:
go run main.go -n result main.go
结果:
48: var result grep.MatchResult
52: result, err = grep.GrepRecursive(pattern, filePath, options)
57: result, err = grep.Grep(pattern, filePath, options)
64: fmt.Println(grep.GrepCount(result))
66: printResult(result, *lineNumberFlag)
70: func printResult(result grep.MatchResult, lineNumberOption bool) {
72: fileCount := len(result)
74: for filePath, items := range result {
使用正则:
go run main.go -n "\br[a-z]+t" main.go
结果:
23: lineNumberFlag := flag.Bool("n", false, "line-number\nEach output line is preceded by its relative line number in the file, starting at line 1. This option is ignored if -count is specified.")
48: var result grep.MatchResult
52: result, err = grep.GrepRecursive(pattern, filePath, options)
57: result, err = grep.Grep(pattern, filePath, options)
64: fmt.Println(grep.GrepCount(result))
66: printResult(result, *lineNumberFlag)
70: func printResult(result grep.MatchResult, lineNumberOption bool) {
72: fileCount := len(result)
74: for filePath, items := range result {
反向匹配:
go run main.go -v -n result main.go
结果:
1: package main
2:
3: import (
4: "flag"
5: "fmt"
6: "log"
7: "os"
...
19:
20: // Optional flags
21: countFlag := flag.Bool("c", false, "count\nOnly a count of selected lines is written to standard output.")
22: ignoreCaseFlag := flag.Bool("i", false, "ignore-case\nPerform case insensitive matching. By default, it is case sensitive.")
23: lineNumberFlag := flag.Bool("n", false, "line-number\nEach output line is preceded by its relative line number in the file, starting at line 1. This option is ignored if -count is specified.")
24: recursiveFlag := flag.Bool("r", false, "recursive\nRecursively search subdirectories listed.")
25: invertMatchFlag := flag.Bool("v", false, "invert-match\nSelected lines are those not matching any of the specified patterns.")
...
80: if lineNumberOption {
81: fmt.Printf("%d: %s\n", item.LineNumber, item.Line)
82: } else {
83: fmt.Println(item.Line)
...
不区分大小写匹配:
go run main.go -i only main.go
结果:
countFlag := flag.Bool("c", false, "count\nOnly a count of selected lines is written to standard output.")
递归匹配:
go run main.go -r count .
结果:
main.go:
countFlag := flag.Bool("c", false, "count\nOnly a count of selected lines is written to standard output.")
lineNumberFlag := flag.Bool("n", false, "line-number\nEach output line is preceded by its relative line number in the file, starting at line 1. This option is ignored if -count is specified.")
if *countFlag {
pkg/grep/search.go:
count := 0
count += len(v)
return count