Search and Replace: Finding Things and Changing Them
Search
/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.
Incremental search
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.