Right, let’s talk about vim’s search and replace. This is where you graduate from just editing text to performing text surgery. It’s powerful, it’s precise, and if you’re not careful, it will absolutely mangle your file while you stare at the screen in silent horror. I’ve been there. We’ll make sure you avoid that.

The two commands we’re dealing with are / for search and :%s/old/new/ for search-and-replace. They’re related, but think of / as your targeting system and :%s as the ordinance you fire after you’ve locked on.

Finding Your Way with /

First, the search. You hit / and suddenly your cursor jumps to the command line at the bottom, waiting for a pattern. You type what you’re looking for (/function) and hit Enter. Vim will jump to the next occurrence. This is your basic find.

The magic comes with what you do next. Hit n to go to the next occurrence. Hit N (Shift+n) to go to the previous occurrence. This is the killer feature. You can scan through every match in your file without ever taking your hands off the home row.

Why this matters: It’s your reconnaissance. Before you ever run a replace command, you should be scouting with / and n. You need to see what you’re about to change. Blindly running :%s is like using a chainsaw in the dark.

/console.log

Hit Enter, then press n a few times. See all those pesky debug logs? Now you know what you’re dealing with.

The Grand Replace: :s and :%s

Now for the main event. The substitute command is :s. By itself, it only works on the current line. That’s often useless. The real power is when you prefix it with %, which means “across the entire file”.

The basic structure is :%s/old/new/. It finds old and replaces it with new. But this only replaces the first occurrence on each line. That’s almost never what you want. This is one of those questionable choices. Why that’s the default? I don’t know. It’s vim. It’s from a time when computers had less memory than your smartwatch.

To make it global per line, you add the g (global) flag at the end: :%s/old/new/g. This replaces every occurrence of old with new on every line. This is what you’ll use 95% of the time.

" Replace the first 'cat' on every line with 'dog'
:%s/cat/dog/

" Replace EVERY 'cat' on every line with 'dog'
:%s/cat/dog/g

Confirming Your Changes (So You Don’t Cry)

Here’s the best practice that will save your bacon: use confirmation. By adding the c flag (e.g., :%s/old/new/gc), vim will ask you before making each change. It’s your safety net.

You’ll get a prompt like this:

replace with dog (y/n/a/q/l/^E/^Y)?

Here’s your cheat sheet:

  • y: Yes, replace this one.
  • n: No, skip this one.
  • a: All, replace this one and all remaining without asking. (The “I’ve seen enough, let’s do this” button).
  • q: Quit the whole operation. Abort!
  • l: Replace this one and then quit. (Last one).
" Safely replace all 'foo' with 'bar', with a prompt for each one.
:%s/foo/bar/gc

Escaping the Delimiter Dungeon

You don’t have to use / as your delimiter. This is a lifesaver when your pattern or replacement string contains a slash. Trying to replace a URL? Yeah, have fun escaping all those slashes: :%s/https:\/\/example.com\/api/https:\/\/new.example.com\/v2\/api/g. It’s a mess.

Instead, just use a different delimiter. Any punctuation character works. I use # most often.

" The messy way (don't do this)
:%s/https:\/\/old.example.com/https:\/\/new.example.com/g

" The clean, brilliant way (do this)
:%s#https://old.example.com#https://new.example.com#g

Using Regex Superpowers

Vim’s search patterns are regular expressions, and this is where the real power lies. Let’s say you want to add a semicolon to the end of every line that starts with let, but only if it doesn’t already have one.

The $ matches the end of a line. The ; is what we’re adding. The \zs is vim’s magic: it means “start the match here”. So we’re matching the end of the line, but only on lines that begin with let, and we’re saying the actual thing we want to replace is the “nothing” at the end of the line, which we then replace with a semicolon.

" Add a semicolon to the end of lines starting with 'let' that don't have one
:%s/^let.*[^;]$/&;/g
" A more precise way using \zs
:%s/^let.*\zs$/;/g

Always test your regex with / first. Scout the target. Then, and only then, arm the :%s command. Your future self, staring at an un-ruined file, will thank you.