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",
	"REC-SVG11-20110816",
	"clojure-doc-1.8",
	"css_2.1_spec",
	"css_transitions",
	"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

Golang

Compile and Run

String

Types and Values

Branching and Loop

Data Structure

Function

Misc

Examples

Reference