Golang: Find String (grep)

By Xah Lee. Date: .

Here's a script that does find string or multiple strings in all files in a dir.

// find strings in a dir. can be a list of strings
// version 2019-10-13
// website: http://xahlee.info/golang/goland_find_string.html

package main

import (
	"fmt"
	"io/ioutil"
	"os"
	"path/filepath"
	"regexp"
	"strings"
	"time"
	"unicode/utf8"
)

// inDir is dir to start. must be full path
var inDir = "/Users/xah/web/xahlee_info/kbd/"

var dirsToSkip = []string{
	".git"}

// list of string to search
// each item is intepreted as regex
var findList = []string{
	"haskell",
	"lisp",
	"perl",
	"python",
}

// fnameRegex. only these are searched
const fnameRegex = `\.html$`

// number of chars (actually bytes) to show before the found string
const charsBefore = 100
const charsAfter = 100

const fileSep = "ff━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n"

const occurSep = "oo────────────────────────────────────────────────────────────\n"

const occurBracketL = '〖'
const occurBracketR = '〗'

const posBracketL = '❪'
const posBracketR = '❫'

const fileBracketL = '〘'
const fileBracketR = '〙'

var bigRegexStr = strings.Join(findList, "|")

// var rgx = regexp.MustCompile(regexp.QuoteMeta(bigRegexStr))
var rgx = regexp.MustCompile(bigRegexStr)

func printSliceStr(sliceX []string) error {
	for k, v := range sliceX {
		fmt.Printf("%v %v\n", k, v)
	}
	return nil
}

// scriptPath returns the current running script path
// version 2018-10-07
func scriptPath() string {
	name, errPath := os.Executable()
	if errPath != nil {
		panic(errPath)
	}
	return name
}

func max(x int, y int) int {
	if x > y {
		return x
	} else {
		return y
	}
}
func min(x int, y int) int {
	if x > y {
		return y
	} else {
		return x
	}
}

// stringMatchAny return true if x equals any of y
func stringMatchAny(x string, y []string) bool {
	for _, v := range y {
		if x == v {
			return true
		}
	}
	return false
}

func doFile(path string) error {
	var textBytes, er = ioutil.ReadFile(path)
	if er != nil {
		panic(er)
	}

	var indexes = rgx.FindAllIndex(textBytes, -1)

	var bytesLength = len(textBytes)
	if len(indexes) != 0 {
		for _, k := range indexes {
			var foundStart = k[0]
			var foundEnd = k[1]
			var showStart = max(foundStart-charsBefore, 0)
			for !utf8.RuneStart(textBytes[showStart]) {
				showStart = max(showStart-1, 0)
			}

			var showEnd = min(foundEnd+charsAfter, bytesLength-1)
			for !utf8.RuneStart(textBytes[showEnd]) {
				showEnd = min(showEnd-1, bytesLength)
			}

			// 			fmt.Printf("%s〖%s〗%s\n", textBytes[showStart:foundStart],
			fmt.Printf("%c%d%c %s%c%s%c%s\n",
				posBracketL,
				utf8.RuneCount(textBytes[0:foundStart+1]),
				posBracketR,
				textBytes[showStart:foundStart],
				occurBracketL,
				textBytes[foundStart:foundEnd],
				occurBracketR,
				textBytes[foundEnd:showEnd])
			fmt.Println(occurSep)
		}
		fmt.Printf("%v %c%v%c\n", len(indexes), fileBracketL, path, fileBracketR)
		fmt.Println(fileSep)
	}
	return nil
}

func main() {

	fmt.Println("-*- coding: utf-8; mode: xah-find-output -*-")
	fmt.Printf("%v\n", time.Now())
	fmt.Printf("Script: %v\n", filepath.Base(scriptPath()))
	fmt.Printf("in dir: %v\n", inDir)
	fmt.Printf("file regex filter: %v\n", fnameRegex)
	fmt.Printf("findList:\n")
	printSliceStr(findList)
	fmt.Println()
	fmt.Println("~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n")

	var pWalker = func(pathX string, infoX os.FileInfo, errX error) error {
		// first thing to do, check error. and decide what to do about it
		if errX != nil {
			fmt.Printf("error 「%v」 at a path 「%q」\n", errX, pathX)
			return errX
		}
		if infoX.IsDir() {
			if stringMatchAny(filepath.Base(pathX), dirsToSkip) {
				return filepath.SkipDir
			}
		} else {
			var x, err = regexp.MatchString(fnameRegex, filepath.Base(pathX))
			if err != nil {
				panic("stupid MatchString error 59767")
			}
			if x {
				doFile(pathX)
			}
		}
		return nil
	}
	err := filepath.Walk(inDir, pWalker)
	if err != nil {
		fmt.Printf("error walking the path %q: %v\n", inDir, err)
	}
	fmt.Println("\nDone.")
}

Emacs Integration

This script is integrated with emacs.

If you use emacs:

  1. Install Emacs: xah-find.el, Find Replace in Pure Elisp
  2. bookmark the golang script so you can easily open it when you need find replace. [see Emacs: Bookmark]
  3. Open the golang script, change input dir and find replace strings.
  4. Alt+x xah-run-current-file to run the script. [see Emacs: Run Current File]
  5. In output, Alt+x xah-find-output-mode, to highlight result, and press enter on highlighted find/replace string text or file path to open the file.

If you are using xah-fly-keys, many of the command above is already there. You still need to install xah-find.el for coloring the output.

[see Emacs: Xah Fly Keys]

Find Replace Scripts

  1. Golang: Find String (grep)
  2. Golang: Find Replace Script
  3. Python: Find Replace in a Dir
  4. Python: Find Replace by Regex
  5. Perl: Find Replace String Pairs in Directory
  6. Elisp: Write grep
  7. Emacs: xah-find.el, Find Replace in Pure Elisp

If you have a question, put $5 at patreon and message me.

Golang

  1. Compile, Run
  2. Source Encoding
  3. Package, Import
  4. Comment
  5. Print
  6. String
  7. String Functions
  8. Print String
  9. String Backslash Escape
  10. Rune
  11. Variable
  12. Zero Value
  13. Constant
  14. If Then Else
  15. Switch/Case
  16. Loop
  17. Basic Types
  18. Array
  19. Slice
  20. Map
  21. Struct
  22. Function
  23. Closure
  24. Pointer
  25. String, Byte Slice, Rune Slice
  26. regexp
  27. Read File
  28. Write to File
  29. Walk Dir
  30. Check File Exist
  31. System Call
  32. Get Script Path
  33. Defer
  34. Random Number

Examples

  1. Match Any Regexp
  2. Find String
  3. Find Replace
  4. Validate Links
  5. Generate Sitemap

Reference

  1. Go Spec