How do I pipe a range of lines through an external shell command and replace them with the output?
Answer
:{range}!{cmd}
Explanation
:!{cmd} with a range sends those lines as stdin to the shell command and replaces them with the command's stdout. This is Vim's filter mechanism for arbitrary line ranges — from a single line to the whole file — transforming text with any Unix tool without leaving Vim.
Syntax
:{range}!{command}
Common range shortcuts:
.— current line::.!cmd%— entire file::%!cmd'<,'>— visual selection (auto-filled on:)1,10— lines 1–10.,+4— current line and next 4 lines
Examples
Sort the entire file:
:%!sort
Format JSON for the whole buffer:
:%!python3 -m json.tool
Base64-encode the current line:
:.!base64
Run a custom script on lines 5–20:
:5,20!./transform.py
Pretty-print XML in a selection:
:'<,'>!xmllint --format -
Tips
- If the command fails or produces no output, Vim replaces the range with nothing — use
uimmediately to undo !in normal mode also works:!!cmdfilters the current line;!{motion}cmdfilters the motion's range- The command sees the exact bytes from the buffer, including any trailing newlines
- This is how Vim integrates with the Unix philosophy: every tool that reads stdin and writes stdout becomes a buffer transformation