Vim Search and Replace, Properly

September 2, 2026 in Vim3 minutes

Where the change lands, what it matches, what it becomes, and how it behaves. Four parts, and each one is worth five minutes.

Nearly everyone learns this one line:

:%s/old/new/g

And then stops. Which is a shame, because that line is four separate ideas bolted together, and each one is independently useful.

Read it as: where / what / becomes / how.

Where: the range

The bit before the s decides where the change lands. % means the whole file, but it is only one of the options:

RangeMeans
:sthis line only
:%sthe whole file
:10,20slines 10 to 20
:.,+5sthis line and the next five
:.,$sfrom here to the end
:'<,'>swhatever you just selected in visual mode

That last one is the one to remember. Select a block, type :, and Vim fills in the range for you. Now your replace is scoped to exactly what you highlighted, which is far safer than % and a hope.

What: matching what you actually mean

The most common mistake in the world of find and replace:

:%s/total/subtotal/g

That also matches the total inside subtotals and grand_total. Use word boundaries:

:%s/\<total\>/subtotal/g

\< means “a word starts here” and \> means “a word ends here”. This single habit prevents most search-and-replace accidents.

A few more pieces worth knowing:

PatternMatches
\d\+one or more digits
\s*any amount of whitespace
^ / $start / end of the line
.\{-}as few characters as possible
\cignore case, just for this search

Becomes: the replacement side

This is where it stops being find-and-replace and starts being a small transformation language.

Wrap part of the pattern in \( and \) and you can reuse it as \1:

:%s/"\(\w\+\)":/"\U\1\E":/g

That says: find a quoted key, and upper-case whatever was inside it.

A JSON snippet with every key upper-cased in one command

\U upper-cases everything after it, \L lower-cases, and \E ends the change. & on its own means “the whole match”.

And if you want arithmetic, prefix the replacement with \= and Vim evaluates it as an expression:

:%s/\d\+/\=submatch(0)*2/g

Every number in the file, doubled.

How: the flags

FlagEffect
gevery match on the line, not just the first
cconfirm each one before changing it
iignore case for this substitute
ncount the matches and change nothing

n is the underrated one. Before running a risky replace, run it with n first:

:%s/pattern//gn

Vim reports how many matches there are and touches nothing. If the number surprises you, your pattern is wrong, and you just found out for free.

c is the other safety net. It stops at each match and offers y to change it, n to skip, a to do the rest, q to stop.

The slash is not special

If your pattern is full of slashes, stop escaping them. Any character can be the delimiter:

:%s#/usr/local#/opt#g

Much easier to read than the version with six backslashes in it.

One habit worth forming

Search first, then substitute:

/\<total\>
:%s//subtotal/g

Leaving the pattern empty in the substitute reuses your last search. So you can search, see every match highlighted, confirm with your own eyes that it matches what you expected, and only then replace.

That ordering has saved me from more bad replaces than any other habit.


This is one lecture from Vim & Neovim Mastery, Build a Modern IDE From Scratch, where every command is typed on camera in a real terminal.