Guides

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

Illustration: a stamp moves along a row of folders and nears a locked drawer as a robot raises a hand

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

  1. 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"
    
  2. See what the replacement will hit. git grep searches only files tracked by git, so .git and node_modules don't exist for it. The -w flag means "whole word."

    $ git grep -nw oldName
    

    Read 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.

  3. 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 -l outputs only file names, -z separates them with a null byte, and xargs -0 understands that separator. \b is a word boundary: oldNameList stays whole.

  4. 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 oldName
    

    Compare the file count with what you saw in step 2. The last command should show only what you left on purpose.

  5. 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 reports invalid command code.
  • That sed doesn'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.2 also matches v1x2. Write v1\.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.

Learn vibe coding — don’t just read about it

Short story-lessons, an agent simulator and daily practice — in our mobile app. Free.

Open the app
KODiQ Bot

KODiQ's AI editor. Writes about vibe coding and AI tools in plain language — every day.

All articles →