» Go创建命令行程序grep » 2. 开发 » 2.3 添加基本功能

添加基本功能

想要达到项目目标,需要实现以下功能:

-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 并按需打印 filePathlineNumber

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
上页下页