Skip to content

A fast find & replace shell command written in Go.

License

Notifications You must be signed in to change notification settings

dolph/find-replace

Repository files navigation

find-replace

A fast find & replace shell command.

Usage

Recursively find and replace the string alpha with beta in both file names and file contents:

$ find-replace alpha beta
Rewriting ./hello-world
Renaming ./alphabet to betabet
  • Files with matching contents in the current working directory are atomically rewritten.
  • Files and directories are renamed.
  • Searches are performed recursively from the current working directory.
  • Searches are case sensitive.
  • .git/ directories are skipped.
  • Binary files are ignored.

Goal

The goal of this project is to improve on a bash snippet that I've relied on for years, by making it faster. The bash:

#!/bin/bash
set -ex
find . -type f -not -path './.git/*' -exec sed -i "s/$1/$2/g" '{}' \;
find . -iname "*$1*" -not -path "./.git/*" -exec rename "$1" "$2" '{}' \;

Benchmarking

In order to recursively rename files & directories with the bash snippet above, you have to run it until it stops failing (because it's renaming directories that it has not traversed yet):

First attempt:

+ find . -type f -not -path './.git/*' -exec sed -i s/virt/subvert/g '{}' ';'
+ + find . -iname '*virt*' -not -path './.git/*' -exec rename virt subvert '{}' ';'
+ find: ‘./doc/api_samples/os-virtual-interfaces’: No such file or directory
+ find: ‘./nova/tests/functional/libvirt’: No such file or directory
+ find: ‘./nova/tests/unit/virt’: No such file or directory
+ find: ‘./nova/virt’: No such file or directory
real    0m5.755s
user    0m1.651s
sys     0m3.893s

Second attempt:

+ find . -type f -not -path './.git/*' -exec sed -i s/virt/subvert/g '{}' ';'
+ + find . -iname '*virt*' -not -path './.git/*' -exec rename virt subvert '{}' ';'
+ find: ‘./nova/tests/unit/subvert/libvirt’: No such file or directory
+ find: ‘./nova/subvert/libvirt’: No such file or directory
real    0m6.680s
user    0m1.593s
sys     0m4.864s

Third attempt:

+ find . -type f -not -path './.git/*' -exec sed -i s/virt/subvert/g '{}' ';'
+ + find . -iname '*virt*' -not -path './.git/*' -exec rename virt subvert '{}' ';'
real    0m6.802s
user    0m1.705s
sys     0m4.866s

So, it effectively takes 3 attempts and a sum total of 19.237 seconds to find and replace "virt" with "subvert" in this example.

find-replace v1.1.2

find-replace v1.1.2 improves on this performance by completing the entire task in a single traversal, 98.2% faster overall (or 94.1% faster per traversal, without errors):

+ find-replace virt subvert
Rewriting ./.zuul.yaml
Rewriting ./HACKING.rst
Rewriting ./README.rst
[866 lines of output removed]
Rewriting ./setup.cfg
Rewriting ./tools/run-tests.py
Rewriting ./tox.ini
real    0m0.351s
user    0m0.152s
sys     0m0.150s

find-replace v1.1.2 is single-threaded.