Search and Replace: Finding Things and Changing Them

/pattern<Enter>    search forward
?pattern<Enter>    search backward
n                  next match (in the direction you searched)
N                  previous match
*                  search forward for the word under the cursor
#                  search backward for the word under the cursor
g*                 like * but matches partial words too
:nohlsearch or :noh     clear the highlighted matches

Search is a mode. After /pattern<Enter>, you are back in normal mode with the cursor on the first match.

With set incsearch (default in modern Vim), matches highlight as you type. Press <Enter> to confirm, <Esc> to cancel.

Combine with set hlsearch (also default) to keep all matches highlighted after the search:

/function

Now every function is highlighted. n cycles through them. When you're done:

:noh

If you want to stop highlighting every search, set nohlsearch. If you want to toggle, map a key in your .vimrc (see chapter 9).

Case sensitivity

Two options work together:

:set ignorecase    search is case-insensitive
:set smartcase     case-insensitive unless you use capitals

With both set, /alice matches alice, Alice, and ALICE. /Alice matches only Alice. This is what you want in almost every situation.

For a one-off override, add \c or \C in the pattern:

/\calice    case-insensitive (even if ignorecase is off)
/\CAlice    case-sensitive (even if ignorecase is on)

Regex in Vim

Vim's default regex flavour is quirky. Many characters you expect to be special are not, and vice versa:

/foo\+       matches "foo", "fooo", "foooo"  (the + must be escaped)
/foo(bar)    matches "foo(bar)" literally     (parens are not groups)
/foo\(bar\)  matches "foobar", bar is group 1

This is annoying. The fix is very magic mode, which makes the regex flavour close to what you expect from most other tools:

/\vfoo+          + works without escaping
/\v(foo)bar      parens form a group
/\v\d+\.\d+      match a number like 1.5

Start every search and substitute with \v and Vim stops fighting you:

/\vclass\s+(\w+)\s*\(.*\)

That is readable. Without \v:

/class\s\+\(\w\+\)\s*(.*)

That is not.

Use \v. Always.

Substitute

The substitute command replaces matches:

:s/old/new/         replace first match on current line
:s/old/new/g        replace all matches on current line
:%s/old/new/g       replace all matches in the file
:%s/old/new/gc      replace all, with confirmation

Anatomy:

:  range  s  /  pattern  /  replacement  /  flags

Ranges

:s           current line only (default)
:%s          whole file (% means "all lines")
:.s          current line (same as :s)
:$s          last line
:1,10s       lines 1 to 10
:.,+5s       current line through 5 lines below
:.,$s        current line to end of file
:'<,'>s      visual selection (Vim fills this in for you)

Flags

g    global: replace all matches on a line, not just the first
c    confirm: ask before each replacement
i    ignore case
I    do not ignore case
e    no error if pattern not found
n    don't replace, just count matches

c is the flag to reach for when you're unsure. Vim shows each match in turn, offering: y yes, n no, a all remaining, q quit, l last (replace this and stop), <C-e> and <C-y> to scroll context.

Examples

:%s/\vold/new/g              replace every "old" with "new"
:%s/\vfoo/bar/gc             confirm each one
:'<,'>s/\v\s+$//g            remove trailing whitespace from selection
:%s/\v(\w+)\s*=\s*(\w+)/\2 = \1/g    swap left and right of assignments

Backreferences

In the replacement, \1, \2, \3, etc. refer to groups captured in the pattern. \0 is the whole match.

:%s/\v(\w+)@(\w+)/\2@\1/g    swap parts of email-like strings

& also means the whole match:

:%s/\vTODO/[&]/g     wrap every TODO in brackets: TODO → [TODO]

Global Command

:global runs a command on every line that matches a pattern. It is the Swiss Army knife of line-level operations.

:g/pattern/cmd    on every line matching pattern, run cmd
:v/pattern/cmd    on every line NOT matching pattern (inverse)

The command is an ex command, often d (delete) or s (substitute) or normal (run normal-mode keys).

Common recipes

:g/TODO/d              delete every line with "TODO"
:v/TODO/d              delete every line without "TODO"
:g/^$/d                delete every blank line
:g/\v^\s*$/d           delete every whitespace-only line
:g/function/p          print every line with "function"
:g/foo/s//bar/g        replace foo with bar, on foo lines only
:g/^/move 0            reverse the file (move each line to the top)

:g is older than Unix grep and is where grep gets its name: g/re/p prints lines matching a regex. It still works:

:g/class/p     list every line containing "class"

Search and Replace Inside a Visual Selection

Select lines in visual mode (V, then motions). Press : and Vim auto-fills '<,'>, the range for your selection:

:'<,'>s/old/new/g

Also available: visual block mode plus :s restricts the substitution to the block columns. Narrow in, rename just the column you care about, done.

Finding Across Files: A Preview

:vimgrep runs a pattern over multiple files and builds a quickfix list:

:vimgrep /pattern/ **/*.py
:copen                  open the quickfix window
:cnext  :cprev          jump between matches

More in chapter 11. For project-wide search, most people use the fzf plugin or ripgrep integration. See chapter 10.

Common Pitfalls

"My regex has a / in it and Vim is confused." Use a different separator. After the command name, the next character becomes the separator:

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

"My substitution worked once and I want to do the same thing again." :& repeats the last substitute on the current line. :%& repeats on the whole file (but without flags, so consider :%s//<replacement>/g if you need them).

"I made a mistake and replaced too much." u undoes the whole substitute, even across many lines.

"g/foo/d deleted more than I expected." g marks matching lines, then runs the command on each. If your command moves the cursor, subsequent lines can be affected. For delete, this is fine. For anything else, test with a visible command first: :g/foo/p to preview.

"I want to keep the searched text highlighted longer / I want it off." :set nohlsearch turns it off permanently. Map <Esc> in normal mode to :noh<Enter> if you want one-key dismissal (see chapter 9).

Next Steps

Continue to 07-buffers-windows-tabs.md to learn how Vim organises more than one file at once.