How to replace text in many files at once — without breaking your .git folder

The internet is full of this recipe: grep -rl oldName . | xargs sed -i 's/oldName/newName/g'. It looks innocent. I ran it in a small repo where one file is named oldName.js:
$ grep -rl oldName . | xargs sed -i 's/oldName/newName/g'
$ git status --short
D src/newName.js
?? src/oldName.js
Nobody renamed that file. But git is now sure newName.js was deleted and oldName.js came out of nowhere. The command crawled into the internal .git/index file and rewrote the name inside it.
Below is an order of steps where this doesn't happen.
Simplest first: replace in your editor
If you need to replace something once, your editor is probably enough. In VS Code it's Ctrl+Shift+H (Cmd+Shift+H on a Mac). It skips .git and node_modules on its own and shows every change before applying it.
You need the terminal when the replacement has to be repeated, scripted, or handed to an agent. Then go step by step.
Step-by-step replacement in the terminal
-
Commit your current state. Git becomes your undo button. Check
git status: there should be no uncommitted changes.$ git status --short $ git add src/app.js && git commit -m "before rename" -
See what the replacement will hit.
git grepsearches only files tracked by git, so.gitandnode_modulesdon't exist for it. The-wflag means "whole word."$ git grep -nw oldNameRead the list. It's cheaper than cleaning up later. If for now you only need to locate the spot, here's more on finding code in a big project.
-
Replace. This version works on both Linux and macOS and isn't afraid of spaces in file names:
$ git grep -lzw oldName | xargs -0 perl -pi -e 's/\boldName\b/newName/g'Here
git grep -loutputs only file names,-zseparates them with a null byte, andxargs -0understands that separator.\bis a word boundary:oldNameListstays whole. -
Check the result. This is the most important step. Look at the summary and the actual diff:
$ git diff --stat src/a.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) $ git diff $ git grep -c oldNameCompare the file count with what you saw in step 2. The last command should show only what you left on purpose.
-
Run the tests and commit. Don't like it? One command undoes it and nothing is lost:
$ git restore src/a.js
What you end up with
The replacement touches only project files and only whole words. The .git folder stays untouched. Before committing you see every edit, and undoing takes a second.
You get a repeatable command you can keep in your notes or drop into a script.
Why not sed
On Linux the short form works too: git grep -lw oldName | xargs sed -i 's/\boldName\b/newName/g'. But macOS has two surprises waiting.
- macOS ships a different
sed. It needs a separate argument after-i:sed -i ''. Without one it takes your expression as a backup extension and reportsinvalid command code. - That
seddoesn't understand\b, so a word won't count as a word.
That's why perl is simpler: one command on both systems.
Two more pattern traps that apply to both:
- Slashes in the text. To replace a URL, change the delimiter:
s#http://old#https://new#g. Otherwise you'll have to escape every/. - A dot means any character.
v1.2also matchesv1x2. Writev1\.2.
When text replacement is a bad idea
Text replacement doesn't understand code. It will change the word in strings, comments, and someone else's class with the same name.
To rename a function or variable, your editor's Rename Symbol (F2) is a better fit. It knows where that symbol is actually used. Leave perl for text: links, configs, an old product name.
Same goes when working with an agent. Don't write "replace it everywhere." Try this instead:
Find all occurrences of the word oldName with git grep and show me the list. Replace whole words only, only in src/. Don't touch node_modules or .git. After replacing, show git diff --stat and run the tests.
The agent does the same steps, and you see the result of each.
How do I replace Cyrillic text?
Add the UTF-8 flags: perl -CSD -Mutf8 -pi -e 's/\bстарый\b/новый/g'. Without them \b doesn't treat Russian letters as a word, and the command finds nothing.
What if I need to change file names, not text?
That's a different job with a different risk: two files can end up with the same name. See how to rename many files at once.
What about Windows?
The easiest route is replace in VS Code. For the command line, install Git Bash and the commands above work almost unchanged.
Short story-lessons, an agent simulator and daily practice — in our mobile app. Free.





