Vim Substitution & Global
Substitution patterns and the global command.
Substitution Fundamentals
" BASIC SYNTAX
:[range]s/pattern/replacement/[flags]
" SIMPLEST FORMS
:s/old/new/ " First match on current line
:s/old/new/g " All matches on current line
:%s/old/new/g " All matches in entire file
:%s/old/new/gc " All matches, confirm each
" RANGES
:s/old/new/ " Current line only
:%s/old/new/ " Entire file (% = 1,$)
:1,10s/old/new/ " Lines 1 through 10
:.,$s/old/new/ " Current line to end (. = current)
:.+1,$s/old/new/ " Next line to end
:'<,'>s/old/new/ " Visual selection
" FLAGS
g " Global (all matches on line)
c " Confirm each replacement
i " Case insensitive
I " Case sensitive (override ignorecase)
n " Count matches only (no replace)
e " Suppress error if no match
& " Keep previous flags
" COUNT MATCHES (no replace)
:%s/pattern//gn " Count occurrences
Capture Groups and Backreferences
" BASIC CAPTURE (use \( \) in vim regex)
:%s/\(word\)/[\1]/g " Wrap word: word → [word]
" MULTIPLE GROUPS
:%s/\(\w\+\) \(\w\+\)/\2 \1/g " Swap words: foo bar → bar foo
" NUMBERED REFERENCES
" \1 \2 \3 ... \9
" EXAMPLE: Reformat phone numbers
" Input: 1234567890
" Output: (123) 456-7890
:%s/\(\d\{3}\)\(\d\{3}\)\(\d\{4}\)/(\1) \2-\3/g
" VERY MAGIC MODE (easier regex)
" Use \v to make most chars special (like Perl regex)
:%s/\v(\w+) (\w+)/\2 \1/g " No escaping parens!
" MAGIC MODE COMPARISON
:%s/\(word\)/[\1]/g " Default magic
:%s/\v(word)/[\1]/g " Very magic (easier)
:%s/\V(word)/[\1]/g " Very nomagic (literal)
" ENTIRE MATCH REFERENCE
:%s/.*/(&)/g " Wrap entire line in parens
" & or \0 = entire match
" NON-CAPTURING GROUPS (not standard, use \%(..))
:%s/\%(un\)\?happy/glad/g " Match "happy" or "unhappy"
Special Replacement Patterns
" CASE CONVERSION
\u " Next char uppercase
\U " Following chars uppercase (until \e or \E)
\l " Next char lowercase
\L " Following chars lowercase
\e \E " End case conversion
" EXAMPLES
:%s/\(error\)/\U\1\E/g " error → ERROR
:%s/\<\(\w\)/\u\1/g " Capitalize first letter of words
:%s/.*/\L&/g " Lowercase entire lines
:%s/\v(\w)(\w*)/\u\1\L\2/g " Title Case Every Word
" SPECIAL CHARACTERS
\r " Newline (in replacement)
\n " Null character (NUL)
\t " Tab
\\ " Literal backslash
\/ " Literal forward slash
\& " Literal ampersand
" INSERT NEWLINE
:%s/, /,\r/g " Split at commas
" Input: a, b, c
" Output: a,
" b,
" c
" EXPRESSION REPLACEMENT
:%s/\d\+/\=submatch(0)+1/g " Increment all numbers
:%s/.*/\=line('.').'. '.submatch(0)/ " Number all lines
:%s/\v(\d+)/\=printf("%03d", submatch(1))/g " Zero-pad numbers
" SUBMATCH() IN EXPRESSIONS
" submatch(0) = entire match
" submatch(1) = first group
" submatch(2) = second group, etc.
Regex Patterns
" CHARACTER CLASSES
\d " Digit [0-9]
\D " Non-digit
\w " Word char [a-zA-Z0-9_]
\W " Non-word char
\s " Whitespace
\S " Non-whitespace
\a " Alphabetic [a-zA-Z]
\l " Lowercase [a-z]
\u " Uppercase [A-Z]
\x " Hex digit [0-9a-fA-F]
" ANCHORS
^ " Start of line
$ " End of line
\< " Start of word
\> " End of word
\%^ " Start of file
\%$ " End of file
" QUANTIFIERS
* " 0 or more
\+ " 1 or more
\? " 0 or 1 (optional)
\{n} " Exactly n
\{n,} " n or more
\{n,m} " n to m
\{-} " 0 or more (non-greedy)
\{-n,m} " n to m (non-greedy)
" ALTERNATION
\| " Or
\(foo\|bar\) " Match foo or bar
" COMMON PATTERNS
\d\+ " One or more digits
\w\+ " One or more word chars
.* " Any characters (greedy)
.\{-} " Any characters (non-greedy)
\s* " Optional whitespace
\s\+ " One or more whitespace
Alternate Delimiters
" PROBLEM: Escaping slashes is ugly
:%s/\/home\/user\/path/\/var\/lib\/path/g " Yuck!
" SOLUTION: Use different delimiter
:%s#/home/user/path#/var/lib/path#g " Cleaner!
" ANY PUNCTUATION WORKS
:%s@pattern@replacement@g
:%s|pattern|replacement|g
:%s!pattern!replacement!g
:%s#pattern#replacement#g
" BEST PRACTICE
" Choose delimiter NOT in your pattern/replacement
" / for most things
" # or | for paths
" @ for URLs
" EXAMPLES
:%s#/etc/ssh/#/etc/sshd/#g " Path replacement
:%s|https://|http://|g " URL scheme change
:%s@\[@{@g " Bracket to brace (Ruby style)
Infrastructure Substitutions
" IP ADDRESS UPDATES
:%s/10\.50\.1\.50/10.50.1.60/g " Replace specific IP
:%s/10\.50\.1\.\d\+/{ip}/g " IP to attribute
:%s/192\.168\.\d\+\.\d\+/10.50.1.&/g " (Won't work - & in IP)
" HOSTNAME CHANGES
:%s/\<old-server\>/new-server/g " Word boundaries prevent partial
:%s/certmgr-01/vault-01/g " Rename server
:%s/\.example\.com/.domusdigitalis.dev/g " Domain change
" YAML VALUE UPDATES
:%s/\(hostname:\s*\)old/\1new/g " Preserve key, change value
:%s/^\(\s*server:\s*\).*$/\1vault-01/g " Replace server value
" SSH CONFIG FORMATTING
:%s/^\s\+/ /g " Normalize indentation to 4 spaces
:%s/\s\+$//g " Remove trailing whitespace
" CONVERT HARDCODED TO ATTRIBUTES
:%s/10\.50\.1\.60/{vault-ip}/g
:%s/10\.50\.1\.20/{ise-01-ip}/g
:%s/10\.50\.1\.90/{bind-ip}/g
" ASCIIDOC ATTRIBUTE SUBSTITUTION
:%s/`\([^`]*\)`/{\1}/g " Inline code to attribute (careful!)
" PORT UPDATES
:%s/:8080/:8443/g " HTTP to HTTPS port
:%s/\<80\>/{port-http}/g " Port to attribute
" CREDENTIAL SANITIZATION
:%s/password:\s*\zs.*/[REDACTED]/g " Redact passwords
:%s/token=\zs[^&]*/[REDACTED]/g " Redact tokens in URLs
" BULK HOSTNAME PATTERN
" vault-01, vault-02, vault-03 → {vault-primary}, {vault-secondary}, etc.
:%s/vault-01/{vault-primary}/g
:%s/vault-02/{vault-secondary}/g
Advanced Substitution Techniques
" CONDITIONAL REPLACEMENT (expression)
:%s/\d\+/\=submatch(0) > 100 ? "high" : "low"/g
" INCREMENT NUMBERS
:%s/\d\+/\=submatch(0)+1/g " All numbers +1
:%s/version:\s*\zs\d\+/\=submatch(0)+1/g " Increment version
" PRESERVE CASE
" Plugin: vim-abolish
" :S/old/new/g - preserves case (old→new, Old→New, OLD→NEW)
" Manual case preservation:
:%s/\<error\>/warning/gI " Exact case match
:%s/\<Error\>/Warning/g " Match capitalization manually
" MULTIPLE SUBSTITUTIONS
:%s/one/two/g | %s/three/four/g " Chain with |
" Or use :global
:g/^/s/old/new/g | s/foo/bar/g " Multiple on each line
" INTERACTIVE MODE (c flag)
:%s/old/new/gc
" y - yes, substitute
" n - no, skip
" a - all remaining
" q - quit
" l - last (substitute and quit)
" ^E ^Y - scroll
" ACROSS MULTIPLE FILES
:args *.conf " Load files
:argdo %s/old/new/ge | update " Substitute in all
" OR with buffers
:bufdo %s/old/new/ge | update
" SUBSTITUTION HISTORY
q/ " Open search history
q: " Open command history (includes :s)
Very Magic Mode
" PROBLEM: Too much escaping
:%s/\(\w\+\)\s\+\(\w\+\)/\2 \1/g " Confusing!
" SOLUTION: \v (very magic)
:%s/\v(\w+)\s+(\w+)/\2 \1/g " Clean!
" VERY MAGIC RULES
" All ASCII except a-zA-Z0-9_ are special
" Much closer to Perl/PCRE regex
" COMPARISON
" Default magic: \( \) \+ \? \|
" Very magic: ( ) + ? |
" COMMON PATTERNS (very magic)
\v(\w+) " Capture word
\v(\d+)\.(\d+)\.(\d+) " Version number
\v^(\s*)(.*) " Capture indentation and content
\v<\w+> " Word boundaries
" LITERAL MATCH: \V (very nomagic)
:%s/\V[text]/replacement/g " [ is literal
" Only \ and delimiter are special
" EXAMPLES
" Default: :%s/\[foo\]/bar/g " Must escape [
" Very nomagic: :%s/\V[foo]/bar/g " Literal [
" MIX MODES
:%s/\v(prefix)\V[literal]\v(suffix)/\1X\2/g
Substitution Gotchas
" WRONG: Using \n in replacement for newline
:%s/,/\n/g " Inserts NUL, not newline!
" CORRECT: Use \r for newline in replacement
:%s/,/\r/g " Actual newline
" NOTE: \n in PATTERN matches newline
" \r in REPLACEMENT inserts newline
" (Yes, it's backwards from sed)
" WRONG: Forgetting to escape regex chars
:%s/10.50.1.60/new/g " . matches ANY char!
" CORRECT: Escape literal dots
:%s/10\.50\.1\.60/new/g
" OR: Use very nomagic
:%s/\V10.50.1.60/new/g " Dots are literal
" WRONG: Greedy matching issues
:%s/<.*>//g " Matches <tag>text</tag> as one!
" CORRECT: Use non-greedy
:%s/<.\{-}>//g " Matches minimal <tag>
" WRONG: Case sensitivity surprise
:%s/Error/Warning/g " Doesn't match ERROR or error!
" CORRECT: Use flag or setting
:%s/Error/Warning/gi " i = case insensitive
" OR
:set ignorecase
:%s/Error/Warning/g
" WRONG: & in replacement is special
:%s/old/new & improved/g " & = matched text!
" Result: old → new old improved
" CORRECT: Escape & in replacement
:%s/old/new \& improved/g " Literal &
" Result: old → new & improved
" WRONG: No range = current line only
:s/old/new/g " Only current line!
" CORRECT: Use % for entire file
:%s/old/new/g
Quick Reference
" ESSENTIAL SUBSTITUTION COMMANDS
:%s/old/new/g " Replace all in file
:%s/old/new/gc " Replace with confirm
:%s/old/new/gi " Case insensitive
:s/old/new/g " Current line only
:'<,'>s/old/new/g " Visual selection
" CAPTURE GROUPS
:%s/\(word\)/[\1]/g " Wrap word
:%s/\v(\w+)/[\1]/g " Same, very magic
& or \0 " Entire match
\1 \2 \3 " Group references
" CASE CONVERSION
\u\U\l\L\e\E " Upper/lower conversion
" ANCHORS
^ $ \< \> " Line/word boundaries
" SPECIAL IN REPLACEMENT
\r " Newline (NOT \n!)
\\ " Literal backslash
\& " Literal &
\=expr " Expression result
" COMMON OPERATIONS
:%s/\s\+$//g " Remove trailing whitespace
:%s/^\s\+//g " Remove leading whitespace
:%s/\n\n\+/\r\r/g " Remove extra blank lines
:%s/\t/ /g " Tabs to spaces
Global Command Fundamentals
" BASIC SYNTAX
:[range]g/pattern/command
:[range]g!/pattern/command " Inverse (or :v)
:[range]v/pattern/command " Same as g!
" HOW IT WORKS
" 1. Mark all lines matching pattern
" 2. Execute command on each marked line
" SIMPLE EXAMPLES
:g/pattern/d " Delete matching lines
:g/TODO/p " Print matching lines
:v/KEEP/d " Delete NON-matching lines
:g/^$/d " Delete empty lines
:g/^\s*$/d " Delete blank lines (whitespace only)
" RANGE EXAMPLES
:1,100g/pattern/d " Lines 1-100 only
:'<,'>g/pattern/d " Visual selection
:.,$g/pattern/d " Current line to end
" DEFAULT COMMAND IS :p (print)
:g/error/ " Same as :g/error/p
Delete Operations
" DELETE MATCHING LINES
:g/pattern/d " Delete lines containing pattern
:g/DEBUG/d " Remove debug statements
:g/console\.log/d " Remove console.log
:g/^#/d " Remove comment lines
:g/TODO:/d " Remove TODO comments
" DELETE NON-MATCHING LINES
:v/pattern/d " Keep only matching lines
:v/ERROR\|WARN/d " Keep only errors and warnings
:v/^\s*[^#]/d " Keep only non-comment lines
" DELETE BLANK/EMPTY LINES
:g/^$/d " Empty lines (nothing)
:g/^\s*$/d " Blank lines (whitespace only)
:g/^\s*#.*$/d " Comment-only lines
" DELETE RANGE AFTER MATCH
:g/pattern/,+5d " Delete match and next 5 lines
:g/START/,/END/d " Delete from START to END (inclusive)
:g/^function/+1,/^function\|%$/d " Delete function bodies
" DELETE DUPLICATES (keeping first)
:g/^\(.*\)$\n\1$/d " Delete duplicate consecutive lines
" Better: use :sort u
:%sort u " Sort and remove duplicates
Copy and Move Operations
" COPY MATCHING LINES
:g/pattern/t$ " Copy to end of file
:g/TODO/t0 " Copy to start of file
:g/ERROR/t. " Copy after current line
" MOVE MATCHING LINES
:g/pattern/m$ " Move to end of file
:g/^#/m0 " Move comments to top
:g/import/m0 " Move imports to top
" COPY TO REGISTER
:g/pattern/y A " Yank to register 'a' (append)
:let @a='' " Clear register first
:g/ERROR/y A " Collect all errors
"ap " Paste collected lines
" COPY TO NEW BUFFER
:g/pattern/y A " Collect lines
:new " New buffer
"ap " Paste
" EXAMPLE: Extract all function definitions
:g/^function\|^def\|^fn /y A
:new | "ap
" REVERSE LINE ORDER
:g/^/m0 " Move each line to top (reverses)
Substitution with Global
" RUN SUBSTITUTION ON MATCHING LINES
:g/pattern/s/old/new/g
" EXAMPLE: Only change in function definitions
:g/^function/s/var/let/g " var→let only in function lines
" EXAMPLE: Update only commented lines
:g/^#/s/old/new/g " Replace in comments only
" EXAMPLE: Fix indentation only in code
:v/^\s*#/s/\t/ /g " Tab→spaces, skip comments
" MULTIPLE SUBSTITUTIONS
:g/pattern/s/one/two/g | s/three/four/g
" EXAMPLE: Normalize server entries
:g/^Host /s/\s\+/ /g | s/ $// " Collapse spaces, trim trailing
" CHAIN WITH OTHER COMMANDS
:g/error/s/old/new/g | d " Substitute then delete line
:g/warn/s/warn/warning/g | m$ " Fix text then move to end
" INCREMENT NUMBERS IN MATCHING LINES
:g/version:/s/\d\+/\=submatch(0)+1/g
" CONDITIONAL SUBSTITUTION
:g/pattern/if getline('.') =~ 'condition' | s/old/new/ | endif
Normal Command Execution
" EXECUTE NORMAL MODE COMMANDS
:g/pattern/normal command
" EXAMPLE: Add semicolons
:g/^[^#]/normal A; " Append ; to non-comment lines
" EXAMPLE: Comment lines
:g/DEBUG/normal I#
" EXAMPLE: Run macro on matches
:g/pattern/normal @a " Apply macro 'a' to matches
" EXAMPLE: Indent matching lines
:g/class/normal >>
" EXAMPLE: Change word on matching lines
:g/TODO/normal cwDONE
" EXAMPLE: Delete to end of line
:g/pattern/normal D
" EXAMPLE: Visual operations
:g/pattern/normal viw"ay " Yank word on matching lines
" COMPLEX NORMAL COMMANDS
" Use Ctrl+V to insert literal control chars
:g/pattern/normal 0f=lywA = <C-R>0<Esc>
" Or use execute with escaped chars:
:g/pattern/exe "normal 0f=lywA = \<C-R>0\<Esc>"
" PRACTICAL: Duplicate line and modify
:g/template/normal yyp:s/template/instance/g
Execute External Commands
" FILTER THROUGH EXTERNAL COMMAND
:[range]!command " Filter range through command
" ENTIRE FILE
:%!sort " Sort entire file
:%!sort -u " Sort and deduplicate
:%!uniq " Remove consecutive duplicates
:%!tac " Reverse line order
" WITH GLOBAL (on matching lines)
:g/pattern/.!command " Filter each matching line
" INSERT COMMAND OUTPUT
:r !command " Read output below cursor
:r !date " Insert current date
:r !ls -la " Insert directory listing
" PRACTICAL EXAMPLES
" Sort imports alphabetically
:/^import/,/^[^import]/sort
" Format JSON
:%!jq . " Pretty print JSON
" Format YAML
:%!yq . " Pretty print YAML
" Format table
:'<,'>!column -t " Align columns
" MD5 current line
:.!md5sum | cut -d' ' -f1
" EXAMPLE: Run linter on file
:w | !shellcheck % " Save and lint
" EXAMPLE: Process with awk
:%!awk '{print NR": "$0}' " Number lines
Infrastructure Editing Patterns
" EXTRACT ACTIVE CONFIG (remove comments)
:v/^\s*[^#\s]/d " Keep non-comment lines
:g/^\s*#/d " Alternative: delete comments
:g/^\s*$/d " Then remove blank lines
" YAML: Find undefined keys
:g/: $/p " Lines with empty values
:g/: \(null\|~\)$/p " Null values
" COLLECT IP ADDRESSES
:g/\d\+\.\d\+\.\d\+\.\d\+/y A " Yank all IP lines
" CONVERT HOSTS TO YAML
:g/^\d/s/\(\S\+\)\s\+\(\S\+\)/- ip: \1\r hostname: \2/
" Input: 10.50.1.60 vault-01
" Output: - ip: 10.50.1.60
" hostname: vault-01
" REFORMAT SSH CONFIG BLOCKS
" Normalize indentation under Host lines
:g/^Host /+1,/^Host \|\%$/s/^\s*/ /
" AUDIT SECURITY
:g/password\|secret\|token\|key/p " Find sensitive lines
:g/chmod 777\|chmod 666/p " Find bad permissions
" EXTRACT SPECIFIC SERVICE BLOCKS
:g/^\[service\]/,/^\[/y A " Yank INI sections
" LOG FILE ANALYSIS
:v/ERROR\|FATAL\|CRITICAL/d " Keep only errors
:g/timestamp/s/.*timestamp:\s*// " Extract timestamps
:g/^\d\{4}-\d\{2}-\d\{2}/!d " Keep only dated lines
" CONFIG CLEANUP
:g/^\s*;/d " Remove INI comments
:g/^\s*#.*REMOVE/d " Remove marked lines
:g/^;.*DEPRECATED/d " Remove deprecated
Combining Global with Other Commands
" CHAIN MULTIPLE OPERATIONS
:g/pattern/d | g/other/d " Delete both patterns
:g/pattern/s/old/new/g | s/foo/bar/g " Multiple substitutions
" CONDITIONAL EXECUTION
:g/pattern/if getline('.') =~ 'condition' | d | endif
" USE WITH RANGES
:g/START/+1,/END/-1d " Delete between markers
:g/^##/+1,/^##/-1 sort " Sort sections
" NESTED GLOBAL (careful!)
" Don't nest :g/:v - use :s or :normal instead
" ALTERNATIVE: Use external tools
:%!grep -v pattern " Delete matching (inverse)
:%!grep pattern " Keep matching only
:%!awk '/pattern/{next}1' " Complex filtering
" COMBINE WITH MARKS
:g/^Chapter/mark C " Mark all chapters
" Then jump with 'C
" COMBINE WITH REGISTERS
qaq " Clear register a
:g/ERROR/y A " Append to register
:new " New buffer
"ap " Paste all errors
" ITERATIVE REFINEMENT
:g/WARNING/ " Review matches
:g/WARNING/s/warn/warning/g " Refine
:g/WARNING/d " Or delete
Global Command Gotchas
" WRONG: Nested :g commands
:g/pattern/g/other/d " Doesn't work as expected!
" CORRECT: Use single pattern or alternatives
:g/pattern\|other/d " Match either
:g/pattern/if getline('.') =~ 'other' | d | endif
" WRONG: Range changes during execution
:g/^/d " Deletes ALL lines (not every other)
" Because line numbers change as lines are deleted
" CORRECT: Use :v for inverse
:v/pattern/d " Safer inverse deletion
" WRONG: Expecting line numbers to be stable
:g/^#/m0 " Results may surprise you
" Lines move during execution, affecting subsequent matches
" CORRECT: For complex moves, yank then insert
:g/^#/y A | new | "ap " Collect then place
" WRONG: Forgetting . in :g/pattern/command
:g/pattern/!sort " NOT what you want
" :g/pattern/! means inverse (:v)
" CORRECT: Use range or execute
:g/pattern/.,.!sort " Sort just that line
:g/pattern/exe "!sort" " Or explicit execute
" WRONG: Using :g with visual selection
:'<,'>g/pattern/d " May delete outside selection!
" :g marks lines FIRST, then deletes, range can shift
" CORRECT: Test with :p first
:'<,'>g/pattern/p " Verify matches
:'<,'>g/pattern/d " Then delete
" WRONG: Expecting :g to work on columns
:g/pattern/normal 0f:d$ " Works on whole line, not column
" CORRECT: For column operations, use visual block
<C-V>G$:s/pattern//g " Column-aware
Quick Reference
" ESSENTIAL GLOBAL COMMANDS
:g/pattern/d " Delete matching lines
:v/pattern/d " Delete NON-matching (keep matches)
:g/pattern/p " Print matching lines
:g/pattern/normal @a " Run macro on matches
:g/pattern/s/old/new/g " Substitute in matching lines
:g/pattern/y A " Yank to register (append)
:g/pattern/t$ " Copy to end of file
:g/pattern/m0 " Move to start of file
" COMMON CLEANUP
:g/^$/d " Delete empty lines
:g/^\s*$/d " Delete blank lines
:g/^\s*#/d " Delete comment lines
:g/DEBUG/d " Delete debug lines
:v/ERROR\|WARN/d " Keep only errors/warnings
" SYNTAX COMPARISON
:g/pattern/command " On matching lines
:v/pattern/command " On NON-matching lines
:g!/pattern/command " Same as :v
" WITH RANGES
:/START/,/END/g/pattern/d " In range only
:'<,'>g/pattern/d " In visual selection