Golang: Script to Find Replace Multi-Pairs of Regex in a Directory
Here's a find replace script for all files in a dir.
Features:
- Do whole directory, including nested sub directories.
- Regex to filter files. e.g. on file extension.
- If a list of file paths is give, only do those files.
- Optional auto backup of changed files.
- More than one find replace pairs.
- Assume file is utf8 encoding.
// -*- 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`}, } // 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
This script is integrated with emacs.
If you use emacs:
- Install Emacs: xah-find.el, Find Replace in Pure Elisp
- bookmark the golang script so you can easily open it when you need find replace. 〔see Emacs: Bookmark〕
- Open the golang script, change input dir and find replace strings.
- Alt+x
xah-run-current-file
to run the script. 〔see Emacs: Run Current File〕 - 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 📦〕