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

  1. Golang: Find Replace Script
  2. Python: Find Replace in a Dir
  3. Python: Find Replace by Regex
  4. Perl: Find Replace String Pairs in Directory
  5. Elisp: Write grep
  6. Emacs: xah-find.el, Find Replace in Pure Elisp

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

Golang

  1. Compile, Run
  2. Source Encoding
  3. Package, Import
  4. Comment
  5. Print
  6. String
  7. Print String
  8. String Backslash Escape
  9. Rune
  10. Variable
  11. Zero Value
  12. Constant
  13. If Then Else
  14. Switch/Case
  15. Loop
  16. Basic Types
  17. Array
  18. Slice
  19. Map
  20. Struct
  21. Function
  22. Closure
  23. Pointer
  24. String, Byte Slice, Rune Slice
  25. regexp
  26. Read File
  27. Write to File
  28. Walk Dir
  29. Check File Exist
  30. System Call
  31. Get Script Path
  32. Defer
  33. Random Number

Examples

  1. Match Any Regexp
  2. Find Replace
  3. Validate Links
  4. Generate Sitemap

Reference

  1. Go Spec