Filter operations
Vim's shell integration turns your editor into a Unix powerhouse. While many users start with Command-Line mode, understanding the progression from explicit commands to keyboard shortcuts unlocks Vim's true potential. This guide follows the natural learning path: from the familiar :{range}!{filter} to the efficient !{motion}{filter} and its handy shortcut [count]!!{filter}.
The Command-Line Mode
The colon-exclamation (:!) is Vim's interface to your shell. It comes in two flavors with crucial differences:
Type 1: Simple Command Execution
:!{cmd}: Execute{cmd}with shell- See
:h :!
:!ls -la # Run command, show output
:!make # Compile, see results
:!git status # Check repository statusKey behavior: Shows output in a pager, returns to editor.
In this post, we don't discuss this type 1, as it is not much fun to view the outputs of the shell commands inside Vim.
Type 2: Range Filtering with :{range}!{filter}
:{range}!{filter} [arg]- See
:h :range!
:.!date # Replace current line with date ouput
:%!sort # Sort entire file
:5,10!fmt # Format lines 5-10
:.,+3!grep foo # Filter next 4 linesKey behavior: Replaces selected text with command output.
We skip type 1 and deep dive on type 2, as it can be extremely useful to call external shell commands to help process the text content from inside Vim.
A filter is a program that accepts text at standard input, changes it in some way, and sends it to standard output.
What "Filter" Means in Vim
Filter in Vim refers to: Sending text through an external program (shell command) and replacing the original text with that program's output.
It's called a "filter" because the external program filters or transforms the input text.
Understanding Ranges
Vim's range syntax gives precise control:
| Range | Meaning | Example |
|---|---|---|
. | Current line | :.!date |
$ | Last line | :$!cat -n |
% | Entire file | :%!jq . |
5,10 | Lines 5-10 | :5,10!sort |
.,+3 | Current + next 3 | :.,+3!column -t |
/start/,/end/ | Pattern to pattern | :/^##/,/^##/!fold |
Real-World :{range}! Examples
" File-wide operations
:%!python3 -m json.tool # Pretty-print JSON
:%!clang-format # Format C++ code
:%!sed 's/foo/bar/g' # Global replacement
" Selective processing
:10,20!awk '[print $1, $3]' # Extract columns
:/TODO/,/DONE/!grep -v "^#" # Remove comments in section
:'a,'b!sort | uniq # Sort and deduplicate marked textThe Normal Mode
Apart from :! in the Command-Line, we also have an alternative, the filter operator !, in the Normal mode.
!{motion}{filter}: Filter{motion}text lines through the external program{filter}
Filter vs Other Operations
| Operation | What it does | Analogy |
|---|---|---|
d (delete) | Remove text | Cutting with scissors |
c (change) | Delete and insert | Cut and paste new text |
y (yank) | Copy text | Photocopying |
! (filter) | Transform via external program | Sending through a machine |
The Bridge: From Command-Line to Normal Mode
Once comfortable with :{range}!, you'll notice a pattern: you often filter text selected by motions. Vim provides a direct path from normal mode.
How ! Works
The ! operator in normal mode follows this pattern:
!{motion/text-object}{shell-command}When you press ! in Normal mode:
- Vim enters operator-pending mode (
-- O-PENDING --) - You provide a motion/text object
- Vim converts it to
:{range}!in command-line - You type the shell command
Example Flow: !ipsort
Normal mode: Press ! → -- O-PENDING --
Type: ip → Selects paragraph
Command line appears: :.,.+2!_
Type: sort <Enter> → Paragraph gets sorted
Status: "3 lines filtered"Common Motions with !
" Line-based motions
!}fmt # To next blank line
!Ggrep pattern # To end of file
!$tr a-z A-Z # To end of line
" Text objects
!ip sort # Inner paragraph
!i" sed 's/old/new/' # Inside quotes
!a[ jq . # Around brackets (JSON/array)
!at fmt # Around tag (HTML/XML)Why Move from :{range}! to !?
- Speed: No need to calculate line numbers
- Visual: See what you're selecting
- Dynamic: Works with cursor position
- Composable: Combine with other Vim commands
The Shortcut !!
The Ultimate Efficiency
If ! is the power move, !! is the special move. It's optimized for the most common case: filtering the current line.
!!{filter}: Filter [count] lines through the external programfilter
What !! Really Means
!! is shorthand for:
- First
!: Filter operator - Second
!: Motion meaning "current line"
So !! in Normal mode will bring you into the Command-line mode :.!.
Everyday !! Magic
!!date # Replace the current line with timestamp
!!tr 'a-z' 'A-Z' # Uppercase line
!!bc -l # Calculate expression
!!rev # Reverse characters
!!fold -w 50 # Fold to 50 columns
!!python3 -c "print(eval('$0'))" # Evaluate PythonWith Counts
2!!sort # Current + next line
3!!fmt # Current + next 2 lines
5!!grep foo # Current + next 4 linesThe Visual Mode
{Visual}!{filter}: Filter the highlighted lines through the external program{filter}
Similarly, pressing ! in Visual mode will bring you into the Command-Line mode :'<,'>!, you can go ahead to type out the shell command.
The Complete Progression
Stage 1: Command-Line Thinking (Beginner)
" I want to sort lines 15-20"
:15,20!sortStage 2: Visual Thinking (Intermediate)
" I want to sort this paragraph"
!ipsortStage 3: Positional Thinking (Advanced)
" I want to sort from here to..."
!}sort # To blank line
!Gsort # To end of file
!apsort # This paragraph with spacingStage 4: Muscle Memory (Expert)
" Quick line transformations"
!!date # Timestamp
!!tr A-Z a-z # Lowercase
!!fmt # ReformatPractical Workflow Examples
Example 1: Processing Log Files
Command-line approach:
:1,100!grep ERROR | sort | uniq -c
:101,200!grep WARNING | cut -d' ' -f3-Normal mode approach:
!Ggrep ERROR | sort | uniq -c # From cursor to end
!}cut -d' ' -f3- # Current sectionExample 2: Code Refactoring
Command-line approach:
:%!sed 's/old_function/new_function/g'
:20,40!clang-formatNormal mode approach:
!iWsed 's/old/new/' # Current word
!aBclang-format # Current code blockExample 3: Data Cleaning
Command-line approach:
:%!tr -d '\r' # Remove Windows line endings
:.,+50!grep -v "^#" # Remove comments in next 51 linesNormal mode approach:
!Gtr -d '\r' # From cursor down
!}grep -v "^#" # To next blank lineUnderstanding the Conversion
What Really Happens
When you use ! in normal mode, Vim:
- Interprets the motion/text object
- Calculates the corresponding line range
- Converts to command-line syntax
- Presents
:{range}!for your command
Conversion Examples
| Normal Mode | Becomes Command-Line | Notes |
|---|---|---|
!ip | :.,.+2! | Paragraph of 3 lines |
!G | :.,$! | Current to end |
!} | :.,/^$/! | To next blank line |
!! | :.! | Current line |
!5j | :.,.+5! | Next 6 lines |
Tips for Each Stage
Mastering :{range}!
- Use
:set numberto see line numbers - Mark lines with
ma,mbthen use:'a,'b! - Visual select (
V) then:!auto-fills range
Transitioning to !
- Start with
!ip(paragraphs are easy to see) - Use visual mode (
V) to confirm selections - Practice common motions:
},G,$
Embracing !!
- Create muscle memory for common tasks
- Use counts:
3!!for multiple lines - Combine with registers for complex commands
Common Pitfalls and Solutions
Problem: grep deletes non-matching lines
" WRONG: Deletes lines without 'pattern'
:.,+5!grep pattern
" RIGHT: Just highlight
:.,+5g/pattern/#
" Or use visual mode
Vjjj:!grep patternProblem: Interactive commands hang
" DON'T:
!!less
!!vim
" DO:
:!less filename # Run externally
:w!less # View buffer in lessProblem: Binary file corruption
" Check first:
:set binary?
" If yes, be careful with filtersAdvanced Techniques
Chaining Commands
" Command-line style
:%!grep -v "^#" | sort | uniq -c | sort -nr
" Normal mode style
!Ggrep -v "^#" | sort | uniq -c | sort -nrUsing Registers
" Store command in register
:let @f = "fmt -w 72"
!ip<C-r>f # Insert from registerConditional Filtering
" Only format if line matches
:if getline('.') =~ 'json'
: .!python3 -m json.tool
:endifCheat Sheet
Quick Reference
COMMAND-LINE MODE:
:!cmd Run command, show output
:%!cmd Filter entire file
:5,10!cmd Filter lines 5-10
:.,+3!cmd Filter next 4 lines
NORMAL MODE:
!ipcmd Filter paragraph
!}cmd Filter to blank line
!Gcmd Filter to end of file
!!cmd Filter current line
3!!cmd Filter 3 lines from cursor
COMMON COMMANDS:
sort Sort lines
fmt Format text
grep Search/filter
sed Find/replace
awk Pattern processing
jq JSON processing
column Align columnsConclusion
The journey from :! to ! to !! mirrors the Vim learning curve:
- Start with
:{range}!{filter}- Explicit, precise, command-line thinking - Graduate to
!{motion}{filter}- Visual, dynamic, normal mode efficiency - Master
[count]!!{filter}- Fluid, positional, muscle memory power
Each form has its place:
- Scripts/configs: Use
:{range}!{filter}for clarity - Interactive editing: Use
!{motion}{filter}for speed - Quick transformations: Use
[count]!!{filter}for efficiency
Remember: They all ultimately become :{range}!{filter} in Vim's engine. The difference is how you specify the range and how quickly you can express your intent.
Start where you're comfortable, but don't stop there. Each step in the progression makes you faster, more fluid, and more powerful in Vim. The shell is your playground—these commands are the gates.