Golang: Regex Find Replace Text in Directory šŸ“œ

By Xah Lee. Date: . Last updated: .

Here's a find replace script for all files in a dir.

Features:

Assume files are encoded by Unicode: UTF-8 Encoding.

// File name: xah_find_replace.go
// Description: find replace string pairs in a dir
// website: http://xahlee.info/golang/goland_find_replace.html
// Created: 2019-01-13
// Version: 2025-07-03

package main

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

// inDir is dir to start. must be full path
const inDir = "c:/Users/xah/web/"
const fnameRegex = `\.html$`
const writeToFile = true
const doBackup = true
const backupSuffix = "~~"

// 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{

	// "c:/Users/xah/web/xahlee_info/comp/all_x_in_unicode.html",
	// "c:/Users/xah/web/xahlee_info/comp/apple_unicode_support.html",
	// "c:/Users/xah/web/xahlee_info/comp/ascii_chars.html",

}

// find and replace pairs
var frPairs = []frPair{

	frPair{
		findStr: `d6Tf3ttt`,
		replaceStr: `xxxttt`},
}

// HHHH------------------------------

type frPair struct {
	findStr    string // find string
	replaceStr string // replace string
}

// 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 printSliceStr(sliceX []string) error {
	for k, v := range sliceX {
		fmt.Printf("%v %v\n", k, v)
	}
	return nil
}

func doFile(path string) error {
	contentBytes, er := os.ReadFile(path)
	if er != nil {
		fmt.Printf("processing %v\n", path)
		panic(er)
	}
	var content = string(contentBytes)
	var changed = false
	for _, pair := range frPairs {
		var found = strings.Index(content, pair.findStr)
		if found != -1 {
			content = strings.Replace(content, pair.findStr, pair.replaceStr, -1)
			changed = true
		}
	}
	if changed {
		fmt.Printf("ā¬%vā­\n", path)

		if writeToFile {
			if doBackup {
				err := os.Rename(path, path+backupSuffix)
				if err != nil {
					panic(err)
				}
			}
			err2 := os.WriteFile(path, []byte(content), 0644)
			if err2 != nil {
				panic("write file problem")
			}
		}
	}
	return nil
}

var pWalker = func(xpath string, xinfo fs.DirEntry, xerr error) error {
	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() {
	scriptPath, errPath := os.Executable()
	if errPath != nil {
		panic(errPath)
	}

	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("Write to file: %v\n", writeToFile)
	fmt.Printf("Do backup: %v\n", doBackup)
	fmt.Printf("fileList:\n")
	printSliceStr(fileList)
	fmt.Printf("Find replace pairs: [%v]\n", frPairs)
	fmt.Println()

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

	fmt.Println()

	if !writeToFile {
		fmt.Printf("Note: writeToFile is %v\n", writeToFile)
	}

	fmt.Printf("%v\n", "Done.")
}

Emacs Integration

see bottom of Golang: Find Text in Directory (grep) šŸ“œ

Find Replace Scripts