Golang: Find Replace Script

By Xah Lee. Date: . Last updated: .

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

Features:

  1. Do whole directory, including nested sub directories.
  2. Use a file extension or regex to filter files.
  3. If a list of file paths is give, only do those files.
  4. Can auto backup of changed files.
  5. Can have more than 1 find replace pairs.
  6. Assume file is utf8 encoding.
// find replace string pairs in a dir
// no regex
// version 2019-01-13
// website: http://xahlee.info/golang/goland_find_replace.html

package main

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

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

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

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

	// sample lines
"/Users/xah/web/ergoemacs_org/emacs/elisp_count-region.html",
"/Users/xah/web/ergoemacs_org/emacs/elisp_run_current_file.html",
"/Users/xah/web/ergoemacs_org/emacs/elisp_delete-current-file.html",

}

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

	frPair{
		fs: `find string here, can be multi lines`,
		rs: `replace string here, can be multi lines`,
	},

	frPair{
		fs: `find string 2`,
		rs: `replace string 2`,
	},

	// add more pairs here

}

// ------------------------------------------------------------

type frPair struct {
	fs string // find string
	rs 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 doFile(path string) error {
	contentBytes, er := ioutil.ReadFile(path)
	if er != nil {
		panic(er)
	}
	var content = string(contentBytes)
	var changed = false
	for _, pair := range frPairs {
		var found = strings.Index(content, pair.fs)
		if found != -1 {
			content = strings.Replace(content, pair.fs, pair.rs, -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 := ioutil.WriteFile(path, []byte(content), 0644)
			if err2 != nil {
				panic("write file problem")
			}
		}
	}
	return nil
}

var pWalker = func(pathX string, infoX os.FileInfo, errX error) error {
	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
}

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: %#v\n", fileList)
	fmt.Printf("Find replace pairs: 「%#v」\n", frPairs)
	fmt.Println()

	if len(fileList) >= 1 {
		for _, v := range fileList {
			doFile(v)
		}
	} else {
		err := filepath.Walk(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

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

Golang

Examples

Reference