Short answer: to find and replace in Vim, press Esc and type :%s/old/new/g, then Enter: % means every line, g every match on a line, and an added c asks before each change. To delete a line, press dd in normal mode; 5dd deletes five lines, and ggdG or :%d deletes everything. Search with /word, then n and N.
/error " search forward for "error", n = next, N = previous
:%s/old/new/g " replace every "old" with "new" in the whole file
:%s/old/new/gc " the same, but confirm each match
dd " delete the current line (5dd deletes five)
:g/DEBUG/d " delete every line that contains DEBUG
ggdG " delete all lines (:%d does the same)The results quoted on this page come from runs with vim -es (Vim’s scriptable Ex mode) on Debian 12, Vim 9.0.1378 from the vim and vim-tiny packages, against a 12-line test file in a scratch directory on 21 October 2026. The c prompt, on-screen messages and highlighting follow Vim’s :help. Saving and quitting have their own page: how to exit vim.
Vim commands cheat sheet
Vim has modes. Keys in normal mode are commands, keys in insert mode are text, and : opens the command line at the bottom of the screen. Esc always brings you back to normal mode, so press it whenever you are unsure where you are.
| Task | Keys or command | Typed in |
|---|---|---|
| Start typing text | i (before cursor), a (after), o (new line below) | Normal mode |
| Back to normal mode | Esc | Any mode |
| Move left, down, up, right | h j k l | Normal mode (arrow keys work too) |
| Next word / previous word | w / b | Normal mode |
| Start / end of the line | 0 / $ | Normal mode |
| First / last line of the file | gg / G | Normal mode |
| Go to line 42 | 42G or :42 | Normal mode / command line |
| Search forward / backward | /word and ?word | Normal mode |
| Next / previous match | n / N | Normal mode |
| Replace in the whole file | :%s/old/new/g | Command line |
| Replace, asking each time | :%s/old/new/gc | Command line |
| Delete the current line | dd | Normal mode |
| Delete five lines | 5dd | Normal mode |
| Delete to the end of the line | D or d$ | Normal mode |
| Delete all lines | ggdG or :%d | Normal mode / command line |
| Delete lines that match | :g/pattern/d | Command line |
| Copy (yank) a line / paste it | yy / p (after), P (before) | Normal mode |
| Select characters, lines, a block | v / V / Ctrl-v | Normal mode |
| Undo / redo | u / Ctrl-r | Normal mode |
| Repeat the last change | . | Normal mode |
| Save / save and quit / quit without saving | :w, :wq, :q! | Command line |
Most normal-mode commands take a count in front: 3w jumps three words, 5dd deletes five lines, 3u undoes three changes.
Find in Vim: /, ?, n, N and *
In normal mode, / opens a search at the bottom of the screen. Type the pattern, press Enter, and the cursor jumps to the next match. ? does the same backwards. n repeats the search in the same direction, N in the other one. At the end of the file the search wraps to the top and Vim says search hit BOTTOM, continuing at TOP; a pattern that is nowhere in the file ends with E486: Pattern not found.
| Command | What it finds |
|---|---|
/error | Next "error" after the cursor, case-sensitive by default |
?error | Previous "error" before the cursor |
n / N | Next match in the same / the opposite direction |
* | Next occurrence of the whole word under the cursor (# goes backwards) |
/error\c | Ignore case for this one search (\C forces case) |
/\<error\> | Whole word only, not "errors" or "myerror" |
:set ignorecase smartcase | Ignore case, unless the pattern has a capital letter |
:set hlsearch | Highlight every match on screen |
:noh | Clear the highlight until the next search (short for :nohlsearch) |
Case is where people get caught. The test file had Error, error and ERROR on lines 5, 6 and 7. /error found only line 6, and n stayed there. /error\c visited lines 5, 6 and 7. With ignorecase and smartcase set, /Error matched only line 5 again, because the capital E switches case sensitivity back on. * on DEBUG searched for \<DEBUG\>, the word with boundaries.
Vim find and replace: :s and :%s
Replacing is the :s (substitute) command on the command line:
:[range]s/pattern/replacement/[flags]Without a range, :s works on the current line only, and without the g flag it changes only the first match on each line. That is why the full “replace all” form is :%s/old/new/g. On a two-line file this is what each variant did:
| Command (cursor on line 1) | Line 1: foo foo foo | Line 2: foo bar |
|---|---|---|
:s/foo/X/ | X foo foo | foo bar |
:s/foo/X/g | X X X | foo bar |
:%s/foo/X/ | X foo foo | X bar |
:%s/foo/X/g | X X X | X bar |
Flags: g, c, i, I, n and e
| Flag | Example | Effect |
|---|---|---|
| g | :%s/old/new/g | Every match on a line, not just the first |
| c | :%s/old/new/gc | Ask before each change |
| i | :%s/error/WARN/gi | Ignore case for this pattern |
| I | :%s/error/WARN/gI | Respect case even with ignorecase set |
| n | :%s/DEBUG//gn | Count matches, change nothing |
| e | :%s/zzz/y/ge | No error if nothing matches (useful in scripts and with argdo) |
In the test file, :%s/error/WARN/gi turned all three spellings on lines 5 to 7 into WARN; with :set ignorecase active, :%s/error/WARN/gI changed only the lowercase one on line 6. :%s/DEBUG//gn printed 2 matches on 2 lines and left the file alone, which is the quickest way to count something in Vim.
With c, Vim highlights each match and waits for a key. Per :help :s_c: y replaces this match, n skips it, a replaces this one and all remaining ones, l replaces this one and stops, and q or Esc stops without replacing it.
Ranges: lines 10 to 20, a selection, a block
| Range | Lines it covers | Example |
|---|---|---|
% | Every line in the file | :%s/a/b/g |
10,20 | Lines 10 to 20 | :10,20s/^/# / |
. | The current line (the default) | :.s/a/b/ |
.,+2 | The current line and the next two | :.,+2d |
.,$ | From the current line to the end | :.,$s/a/b/g |
'<,'> | The last visual selection | :'<,'>s/^/# / |
/start/,/end/ | From the next line matching start to the next line matching end, both searched from the cursor | :/^Error/,/^ERROR/s/^/# / |
You rarely type '<,'> yourself. Select lines with V, press :, and Vim fills it in; then add s/^/# / to comment out exactly the selected lines. In the test, two lines selected with Vj got the # and nothing else did. To limit a replacement to lines that contain some other word, combine it with :g: :g/DEBUG/s/trace/TRACE/ changed trace only on the two DEBUG lines.
Slashes in the pattern: escape them or change the delimiter
Paths are full of slashes, and every one inside the pattern or the replacement needs a backslash. Any other single-byte character except a letter, a digit, \, " or | can be the delimiter instead (per :help pattern-delimiter), and both lines below produced root /srv/www/old;:
:%s/\/var\/www/\/srv\/www/g
:%s#/var/www#/srv/www#g&, \1 and other special replacements
In the replacement, & stands for the whole match, and \1 to \9 for what the first to ninth \( \) group captured. Two runs against the test file:
:%s/[0-9]\+/<&>/g
listen 80; -> listen <80>;
proxy_pass http://127.0.0.1:8080; -> proxy_pass http://<127>.<0>.<0>.<1>:<8080>;
:%s/^\(\w\+\): \(.*\)$/\2 (\1)/
Error: disk full -> disk full (Error)The first one is also a warning: % means every line, including lines you were not thinking about, such as the IP address in proxy_pass. Count with the n flag or confirm with c before you run a wide replacement. Backslashes pile up quickly, so Vim offers “very magic” mode: \v at the start of the pattern makes ( ) + ? special without a backslash. :%s/\v^(\w+): (.*)$/\2 (\1)/ gave the same result as the second command above.
- New line in the replacement: use
\r.:s/; /\r/gsplita; b; cinto three lines.\nmatches a line end in the pattern, but in the replacement it inserts a NUL byte (per:help s/\n), not a line break. - A literal & or ~: write
\&and\~. Unescaped,~inserts the previous replacement string. - Change case:
\uuppercases the next character,\Ueverything up to\E;\land\Ldo the opposite. - Reuse the last search: an empty pattern takes the last one. After
/www,:%s//srv/greplacedwww. Search first, check the highlights, then replace.
Replacing in several files
Vim can loop over files with :argdo. In a folder with three .conf files, this changed port=80 in the two that had it, left port=8080 alone thanks to the $ anchor, and saved each file:
:args *.conf
:argdo %s/port=80$/port=8081/ge | updateThe e flag keeps a file without a match from stopping the loop, and update writes only the files that changed. For anything you want to run again from a script or a deploy, use sed (sed -i 's/old/new/g' file) instead, and find the files first with grep -rl. Vim’s regex syntax is close to sed’s basic regular expressions, so most patterns carry over.
Delete lines in Vim: dd, 5dd, dG and :%d
Deleting in normal mode is the operator d followed by a motion: dw deletes to the next word, d$ to the end of the line, dG to the end of the file. Doubling the operator, dd, means the whole line. Every line in this table was run against the test file:
| Command | Deletes | Mode |
|---|---|---|
x | The character under the cursor | Normal |
dw | From the cursor to the start of the next word | Normal |
D or d$ | From the cursor to the end of the line | Normal |
dd | The current line | Normal |
5dd | Five lines, starting with the current one | Normal |
dG | From the current line to the end of the file | Normal |
dgg | From the current line to the start of the file | Normal |
ggdG | Everything (go to line 1, then delete to the end) | Normal |
:%d | Everything | Command line |
:8,$d | Line 8 to the end | Command line |
:g/DEBUG/d | Every line containing DEBUG | Command line |
:g!/error/d or :v/error/d | Every line that does not contain error | Command line |
:g/^\s*$/d | Empty lines and lines with only spaces or tabs | Command line |
:sort u | Duplicate lines (sorts the file and keeps one of each) | Command line |
“Delete all” has two answers that do the same thing: ggdG from normal mode and :%d from the command line. Both left a 12-line file empty, and u brings the content back as long as you have not quit.
Removing lines that match a pattern with :g
:g/pattern/command runs a command on every line that matches. With d as the command it removes them, and the inverted form keeps only the matches. On the test file:
:g/DEBUG/d removed "DEBUG trace one" and "DEBUG trace two"
:v/\cerror/d kept only "Error: disk full", "error: retry", "ERROR: gave up"
:g/^\s*$/d removed the empty line and the line with three spaces:v is the short form of :g!; both gave the same three lines. Adding \c to the pattern made the filter case-insensitive. This is the Vim equivalent of grep -v, except that it edits the file in place, so look at the result before you save.
Copy and paste: yank and put
| Keys | What it does |
|---|---|
yy | Yank (copy) the current line; 3yy yanks three |
yw / y$ | Yank to the next word / to the end of the line |
p / P | Put (paste) after / before the cursor; a yanked line goes below / above |
yyp | Duplicate the current line |
ddp | Swap the current line with the one below |
"ayy / "ap | Yank into register a, paste from register a |
:reg | Show what every register holds |
In the test, 1G3yyGp copied the first three lines to the end of the file, and 1Gddp swapped lines 1 and 2. The system clipboard registers "+ and "* only work in a Vim built with +clipboard; both Debian 12 builds used here report -clipboard in vim --version. Over SSH, select and copy with your terminal instead. If code pasted from the terminal arrives with a growing staircase indent, :set paste before pasting and :set nopaste afterwards stops the auto-indent.
Visual mode: select first, then act
v selects characters, V whole lines and Ctrl-v a rectangular block. Move to extend the selection, then press an operator: d deletes, y yanks, c replaces, > and < indent, ~ toggles case, : opens the command line with '<,'> already filled in. A block selection with I types the same text on every selected line, which is the fast way to comment out a block:
5G " go to line 5
Ctrl-v " start a block selection
2j " extend it two lines down
I# <Esc> " insert "# " at the start of all three linesThat sequence put # in front of lines 5, 6 and 7 of the test file. The text shows on the first line while you type and appears on the others after Esc.
Undo, redo and repeat
u undoes the last change, Ctrl-r redoes it, and . repeats the last change at the cursor, which pairs well with n: search, change one match, then n. for each further one you want. A whole :%s counts as one change, so a single u after :%s/old/new/g restored the original file in the test. More on undo, and on getting out of the editor cleanly, is in how to exit vim.
.vimrc basics: line numbers and syntax highlighting
Settings typed with :set last until you quit. To keep them, put them in ~/.vimrc, one per line, without the colon:
set number " line numbers (:set nonumber turns them off)
set ignorecase smartcase " case-insensitive search unless you type a capital
set hlsearch incsearch " highlight matches, jump while typing
set expandtab tabstop=4 shiftwidth=4
if has('syntax')
syntax on " colours, where the build supports it
endifBoth Debian builds read this file without errors. The if has('syntax') guard is for vim.tiny, which has no syntax highlighting: a bare syntax on made it exit with an error, and typed on its command line it answered E319: Sorry, the command is not available in this version. Per :help defaults.vim, Vim 8.0 and later load defaults.vim (mouse, incsearch, a scroll margin) only when there is no ~/.vimrc, so creating one switches those defaults off.
vim vs vi on a minimal server
On a server, vi is often not the Vim you know. A Debian install without the full vim package gets vim-tiny, and vi starts it with /etc/vim/vimrc.tiny, which sets compatible to behave like the original vi. Rocky Linux and AlmaLinux ship a small vim-minimal build as vi and the full editor as the vim-enhanced package. What the difference meant in the Debian test:
| vi (vim.tiny, Debian 12) | vim (vim package, Debian 12) | |
|---|---|---|
| Build | Tiny version without GUI | Huge version without GUI |
| :s, :g, dd, visual mode | Yes | Yes |
| Syntax highlighting | No (-syntax) | Yes |
| Vim script (if, let, functions) | No (-eval) | Yes |
| u pressed twice | Undoes, then redoes the same change (vi behaviour) | Undoes two changes |
| System clipboard | No | No (-clipboard) |
The undo row is the one that confuses people: after two substitutions, uu in vi mode ended with the second change still in place, because the second u undid the undo. Check what you have with readlink -f "$(command -v vi)" and vi --version | head -n 3. The full editor is sudo apt install vim on Debian and Ubuntu and sudo dnf install vim-enhanced on Rocky Linux and AlmaLinux. The Linux commands cheat sheet has the everyday commands around the editor.
Editing configs on a VPS with Vim
- Count before you replace.
:%s/old//gnshows how many matches a replacement will touch; an unexpected number usually means the pattern also hits a comment or a second server block. - Test before you reload. Let the service check the file first:
sudo nginx -torsudo sshd -t. An untested typo insshd_configcan lock you out. - Keep a way back. On a VMHeaven VPS, the browser VNC console in the panel stays reachable when SSH is not, and a panel snapshot taken before a larger change restores the whole server.
Frequently asked
How do I find and replace in Vim?
Press Esc, then type :%s/old/new/g and press Enter. % applies it to every line and g to every match on a line; without % only the current line changes. Add c (:%s/old/new/gc) to confirm each replacement, or i to ignore case.
How do I delete a line in Vim?
In normal mode (press Esc first), dd deletes the current line and 5dd deletes five lines from the cursor down. :10,20d deletes lines 10 to 20, and :g/pattern/d deletes every line that contains pattern. Press u to undo.
How do I delete all lines in Vim?
Press Esc, then type ggdG: gg jumps to the first line and dG deletes to the end of the file. :%d on the command line does the same. Save with :w to keep the empty file, or press u to bring the text back.
How do I search in Vim?
In normal mode, type /word and press Enter to jump to the next match; n goes to the next one and N to the previous one. ?word searches backwards, * searches for the word under the cursor, and :set ignorecase smartcase makes searches case-insensitive unless you type a capital letter.