はじめに
前回は、ls
コマンドの一部実装に取り組んでみました。今回は、grep
コマンドの一部実装に取り組んでみます。
目標としては、
- 検索文字列が出現する行を出力し、その検索文字列は区別できるように色づけてあげること
-
-n
オプションで出現位置の行数も出力するようにすること
の2つを設定したいと思います。
前回の記事はこちらから
実装
今回のリポジトリですmm
まず、1つ目の「 my-grep <target> sample.txt
で検索文字列が出現する行を出力すること」を実装してみます。ポイントは、Scanner
を用いて、一行ずつテキストを読み込み、対象の文字列が含まれているか走査している点です。また、対象文字列の色付けにはfatih/color
というライブラリを拝借させていただきました。
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))
}
検証用のテキストファイルの内容を示しておきます。テキストはジャレド・ダイアモンドの「銃・病原菌・鉄」からの引用です。
"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
では、実行してみます。
peoples
という文字列の出現した行がきちんと出力されています!
では、目標の2つ目の-n
オプションも実装してみます。内容としては、flag
ライブラリを利用して、コマンドライン引数を受け取るようにし、あとは単純にループで行数のインクリメントを行っています。
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))
}
では、こちらも実行してみます。
無事、among
という単語が行数と共に出力されていることが分かります!
おわりに
今回は、grep
コマンドの一部実装をしてみました。次回はlinuxの標準コマンドではありませんが、tree
コマンドの実装にチャレンジしてみます!