Golang: Find Text in Directory (grep) πŸ“œ

By Xah Lee. Date: . Last updated: .

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

/*
File name: xah_find.go
Description: find strings in a dir. Can be a list of strings
Home page: http://xahlee.info/golang/goland_find_string.html
Created: 2020-05-24
Version: 2026-07-08
*/

package main

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

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

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

// dir name, not full path. and exact equal, not regex
var dirsToSkip = []string{
	".git",
	// "emacs_manual",
	// "js_es2011",
	// "js_es2015",
	// "node_api",
}

// fileList if not empty, only these are processed. Each element is a full path
var fileList = []string{}

// list of string to search
// each item is intepreted as regex
// use regexp.QuoteMeta to quote them if want plain text
var findList = []string{

	// example: case insensitive
	`(?i)John`,

	// example of literal search
	regexp.QuoteMeta(`.txt`),

}

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

const fileSep = "======================================="
const occurSep = "-------------------------"
const occurBracketL = '⟨'
const occurBracketR = '⟩'
const positionPrefix = "β€’at position: "
const filepathPrefix = "β€’at file: "

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

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 = os.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("%s%c%s%c%s\n%v%d\n",
				textBytes[showStart:foundStart],
				occurBracketL,
				textBytes[foundStart:foundEnd],
				occurBracketR,
				textBytes[foundEnd:showEnd],
				positionPrefix,
				utf8.RuneCount(textBytes[0:foundStart+1]),
			)
			fmt.Println(occurSep, "\n")
		}
		fmt.Printf("count: %v\n%v%v\n", len(indexes), filepathPrefix, path)
		fmt.Println(fileSep, "\n")
	}
	return nil
}

var f_walkfunc = func(xpath string, xinfo fs.DirEntry, xerr error) error {
	// first thing to do, check error. and decide what to do about it
	if xerr != nil {
		fmt.Printf("error [%v] at a path [%q]\n", xerr, xpath)
		return xerr
	}
	if xinfo.IsDir() {
		if stringMatchAny(filepath.Base(xpath), dirsToSkip) {
			return filepath.SkipDir
		}
	} else {
		var x, err = regexp.MatchString(fnameRegex, filepath.Base(xpath))
		if err != nil {
			panic("stupid MatchString error 59767")
		}
		if x {
			doFile(xpath)
		}
	}
	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("fileList:\n")
	printSliceStr(fileList)
	fmt.Printf("findList:\n")
	printSliceStr(findList)
	fmt.Println()
	fmt.Println("~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n")

	if len(fileList) >= 1 {
		for _, v := range fileList {
			doFile(v)
		}
	} else {
		err := filepath.WalkDir(inDir, f_walkfunc)
		if err != nil {
			fmt.Printf("error walking the path %q: %v\n", inDir, err)
		}
	}

	fmt.Println("\nDone.")
}

Emacs Integration

If you use emacs:

If you are using Emacs: Xah Fly Keys πŸ“¦, many of the command above is already there. You still need to install xah-find.el for coloring the output.

Find Replace Scripts