Golang: Script to Find Replace Multi-Pairs of Regex in a Directory

By Xah Lee. Date: . Last updated: .

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

Features:

// -*- coding: utf-8 -*-
// File name: xah_find_replace.go
// Description: find replace string pairs in a dir
// website: http://xahlee.info/golang/goland_find_replace.html
// version 2019-01-13 2022-01-12 2022-07-24 2022-07-29 2022-08-07

package main

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

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

// dir name, not full path. and exact equal, not regex
var dirsToSkip = []string{
	".git",
	"clojure-doc-1.8",
	"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{}

// frPairs is a slice of frPair struct.
var frPairs = []frPair{

	frPair{
		findStr:    `<div class="topicXL">2022-08-07y2xMvkF58t`,
		replaceStr: `d82wY`},
}

// HHH___________________________________________________________________

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

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