LoginSignup
1
0

[Go] grepコマンドを一部実装してみる

Last updated at Posted at 2024-05-19

はじめに

前回は、lsコマンドの一部実装に取り組んでみました。今回は、grepコマンドの一部実装に取り組んでみます。
目標としては、

  1. 検索文字列が出現する行を出力し、その検索文字列は区別できるように色づけてあげること
  2. -nオプションで出現位置の行数も出力するようにすること

の2つを設定したいと思います。

前回の記事はこちらから

実装

今回のリポジトリですmm

まず、1つ目の「 my-grep <target> sample.txtで検索文字列が出現する行を出力すること」を実装してみます。ポイントは、Scannerを用いて、一行ずつテキストを読み込み、対象の文字列が含まれているか走査している点です。また、対象文字列の色付けにはfatih/colorというライブラリを拝借させていただきました。

main.go
package main

import (
	"bufio"
	"fmt"
	"os"
	"strings"

	"github.com/fatih/color"
)

type Line struct {
	Content  string
}

func main() {
	targetString := os.Args[1]
	targetFile := os.Args[2]

	file, err := os.Open(targetFile)
	if err != nil {
		panic("Error opening file")
	}
	defer file.Close()

	scanner := bufio.NewScanner(file)

	matchedLines := []Line{}

	for scanner.Scan() {
		if strings.Contains(scanner.Text(), targetString) {
			matchedLines = append(matchedLines, Line{Content: scanner.Text()})
		}
	}

	listLines(matchedLines, targetString)
}

func listLines(lines []Line, target string) {
	for _, line := range lines {
		fmt.Println(reconstructLine(line.Content, target))
	}
}

func reconstructLine(line string, target string) string {
	green := color.New(color.FgGreen).SprintFunc()
	return strings.ReplaceAll(line, target, green(target))
}

検証用のテキストファイルの内容を示しておきます。テキストはジャレド・ダイアモンドの「銃・病原菌・鉄」からの引用です。

sample.txt
"History followed different courses for
different peoples because of differences among peoples' environments,
not because of biological differences among peoples themselves"
― Jared Diamond, Guns, Germs and Steel: The Fates of Human Societies

では、実行してみます。

image.png

peoplesという文字列の出現した行がきちんと出力されています!

では、目標の2つ目の-nオプションも実装してみます。内容としては、flagライブラリを利用して、コマンドライン引数を受け取るようにし、あとは単純にループで行数のインクリメントを行っています。

main.go
package main

import (
	"bufio"
	"flag"
	"fmt"
	"os"
	"strings"

	"github.com/fatih/color"
)

type Line struct {
	Content  string
+   Position int
}

func main() {
+	isLineNumberCountingEnabled := flag.Bool("n", false, "Count lines")
+	flag.Parse()

+	targetString := flag.Arg(0)
+	targetFile := flag.Arg(1)
-   targetString := os.Args[1]
-	targetFile := os.Args[2]	

	file, err := os.Open(targetFile)
	if err != nil {
		panic("Error opening file")
	}
	defer file.Close()

	scanner := bufio.NewScanner(file)

	matchedLines := []Line{}
+	currentLineNumber := 1

	for scanner.Scan() {
		if strings.Contains(scanner.Text(), targetString) {
-  			matchedLines = append(matchedLines, Line{Content: scanner.Text()})
+			matchedLines = append(matchedLines, Line{Content: scanner.Text(), Position: currentLineNumber})
		}
		currentLineNumber++
	}

+	if *isLineNumberCountingEnabled {
+		listLinesWithLineNumber(matchedLines, targetString)
+		return
+	}

	listLines(matchedLines, targetString)
}

func listLines(lines []Line, target string) {
	for _, line := range lines {
		fmt.Println(reconstructLine(line.Content, target))
	}
}

+func listLinesWithLineNumber(lines []Line, target string) {
+	for _, line := range lines {
+		fmt.Printf("%d: %s\n", line.Position, reconstructLine(line.Content, target))
+	}
+}

func reconstructLine(line string, target string) string {
	green := color.New(color.FgGreen).SprintFunc()
	return strings.ReplaceAll(line, target, green(target))
}

では、こちらも実行してみます。

image.png

無事、amongという単語が行数と共に出力されていることが分かります!

おわりに

今回は、grepコマンドの一部実装をしてみました。次回はlinuxの標準コマンドではありませんが、treeコマンドの実装にチャレンジしてみます!

1
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
0