sed Deep Dive
The Golden Rule
|
Preview before apply. Always.
This is non-negotiable. One wrong |
Understanding sed Architecture
╔══════════════════════════════════════════════════════════════════════════════╗ ║ SED PROCESSING MODEL ║ ╠══════════════════════════════════════════════════════════════════════════════╣ ║ ║ ║ INPUT FILE ║ ║ │ ║ ║ ▼ ║ ║ ┌─────────────────────────────────────────────────────────────────────┐ ║ ║ │ PATTERN SPACE │ ║ ║ │ (Current line being processed - where commands operate) │ ║ ║ └─────────────────────────────────────────────────────────────────────┘ ║ ║ │ ▲ ║ ║ │ (exchange: x, h, H, g, G) │ ║ ║ ▼ │ ║ ║ ┌─────────────────────────────────────────────────────────────────────┐ ║ ║ │ HOLD SPACE │ ║ ║ │ (Auxiliary buffer for multi-line operations) │ ║ ║ └─────────────────────────────────────────────────────────────────────┘ ║ ║ │ ║ ║ ▼ ║ ║ OUTPUT (stdout or file with -i) ║ ║ ║ ║ Processing cycle per line: ║ ║ 1. Read line into pattern space ║ ║ 2. Execute all commands ║ ║ 3. Print pattern space (unless -n) ║ ║ 4. Clear pattern space, repeat ║ ║ ║ ╚══════════════════════════════════════════════════════════════════════════════╝
Command Reference
Delete (d)
# Delete lines matching pattern
sed '/pattern/d' file
# Delete line 5
sed '5d' file
# Delete lines 5-10
sed '5,10d' file
# Delete from pattern to end
sed '/START/,$d' file
# Delete empty lines
sed '/^$/d' file
# Delete lines starting with #
sed '/^#/d' file
# Delete blank and comment lines
sed '/^$/d; /^#/d' file
Insert (i) and Append (a)
# Insert BEFORE line 5
sed '5i\New line inserted before' file
# Append AFTER line 5
sed '5a\New line appended after' file
# Insert before lines matching pattern
sed '/pattern/i\Inserted before match' file
# Append after lines matching pattern
sed '/pattern/a\Appended after match' file
# Multi-line insert (use \ for continuation)
sed '1i\
# Configuration file\
# Generated: 2026-02-09\
# DO NOT EDIT MANUALLY' file
Change (c)
Replace entire line:
# Replace line 5 entirely
sed '5c\This replaces line 5 completely' file
# Replace lines matching pattern
sed '/old_config/c\new_config = enabled' file
Print (p)
# Print matching lines (with -n to suppress default output)
sed -n '/ERROR/p' file
# Print line range
sed -n '10,20p' file
# Print first 10 lines (like head)
sed -n '1,10p' file
# Print last line
sed -n '$p' file
# Print every other line (odd lines)
sed -n '1~2p' file
# Print every 3rd line starting from line 2
sed -n '2~3p' file
Address Ranges
Control WHICH lines commands operate on.
Line Numbers
# Single line
sed '5s/old/new/' file
# Range
sed '5,10s/old/new/' file
# Line 5 to end of file
sed '5,$s/old/new/' file
# First line only
sed '1s/old/new/' file
# Last line only
sed '$s/old/new/' file
Step Intervals
# Every line starting from line 1, step 2 (odd lines)
sed '1~2s/old/new/' file
# Every 5th line starting from line 0
sed '0~5s/old/new/' file
Pattern Addresses
# Lines containing pattern
sed '/ERROR/s/old/new/' file
# Lines NOT containing pattern
sed '/ERROR/!s/old/new/' file
# From first match to second match
sed '/START/,/END/s/old/new/' file
# From line 10 to pattern
sed '10,/STOP/s/old/new/' file
# From pattern to end of file
sed '/BEGIN/,$s/old/new/' file
The Hold Buffer: Multi-Line Operations
The hold space is sed’s secret weapon for complex transformations.
Hold Buffer Commands
| Command | Action |
|---|---|
|
Copy pattern space to hold space (overwrites) |
|
Append pattern space to hold space (with newline) |
|
Copy hold space to pattern space (overwrites) |
|
Append hold space to pattern space (with newline) |
|
Exchange pattern space and hold space |
Reverse Lines (tac implementation)
# Reverse order of lines
sed '1!G;h;$!d' file
# How it works:
# 1!G - For all lines except first, append hold to pattern
# h - Copy pattern to hold
# $!d - Delete pattern unless last line
# Result: Lines accumulate in reverse order
Join Every Two Lines
# Join pairs of lines with space
sed 'N;s/\n/ /' file
# N = append next line to pattern space
# s/\n/ / = replace newline with space
Regex Mastery
Basic Regex (BRE) - Default
. Any single character
* Zero or more of previous
^ Start of line
$ End of line
[abc] Character class
[^abc] Negated character class
\{n\} Exactly n occurrences
\{n,\} n or more
\{n,m\} Between n and m
\(\) Capture group (backreference with \1, \2...)
Extended Regex (ERE) - With -E
sed -E 's/pattern/replacement/' file
+ One or more of previous
? Zero or one of previous
| Alternation (or)
() Grouping (no escaping needed)
{n} Exactly n (no escaping needed)
Escaping Reference
| Character | BRE | ERE (-E) |
|---|---|---|
Literal |
|
|
Literal |
|
|
Literal |
|
|
Literal |
|
|
Literal |
|
|
One or more |
|
|
Zero or one |
|
|
Alternation |
|
|
Grouping |
|
|
Backreferences
# Swap two words
echo "hello world" | sed 's/\(\w*\) \(\w*\)/\2 \1/'
# Output: world hello
# Duplicate matched text
echo "abc" | sed 's/\(.*\)/\1\1/'
# Output: abcabc
# Reference entire match with &
echo "hello" | sed 's/.*/[&]/'
# Output: [hello]
# Extract parts
echo "user@domain.com" | sed 's/\(.*\)@\(.*\)/User: \1, Domain: \2/'
# Output: User: user, Domain: domain.com
Production Scenarios
Configuration File Management
Update Kerberos DC Reference
# Preview
sed -n 's/dc-01\.inside\.domusdigitalis\.dev/home-dc01.inside.domusdigitalis.dev/gp' /etc/krb5.conf
# Apply
sudo sed -i 's/dc-01\.inside\.domusdigitalis\.dev/home-dc01.inside.domusdigitalis.dev/g' /etc/krb5.conf
SSH Hardening
# Disable password authentication
sudo sed -i 's/^#\?PasswordAuthentication.*/PasswordAuthentication no/' /etc/ssh/sshd_config
# Disable root login
sudo sed -i 's/^#\?PermitRootLogin.*/PermitRootLogin no/' /etc/ssh/sshd_config
# Change SSH port
sudo sed -i 's/^#\?Port.*/Port 22022/' /etc/ssh/sshd_config
# Enable key-only auth (multiple changes)
sudo sed -i '
s/^#\?PasswordAuthentication.*/PasswordAuthentication no/
s/^#\?PubkeyAuthentication.*/PubkeyAuthentication yes/
s/^#\?ChallengeResponseAuthentication.*/ChallengeResponseAuthentication no/
' /etc/ssh/sshd_config
Update IP Addresses Across Configs
# Find all files containing old IP
grep -rl "10.50.1.50" /etc/ 2>/dev/null
# Preview changes in each file
for f in $(grep -rl "10.50.1.50" /etc/ 2>/dev/null); do
echo "=== $f ==="
sed -n 's/10\.50\.1\.50/10.50.1.51/gp' "$f"
done
# Apply (with backup)
for f in $(grep -rl "10.50.1.50" /etc/ 2>/dev/null); do
sudo sed -i.bak 's/10\.50\.1\.50/10.50.1.51/g' "$f"
done
Log Processing
Extract Specific Fields
# Extract IP addresses from Apache log
sed -n 's/^\([0-9.]*\) .*/\1/p' /var/log/apache2/access.log
# Extract timestamps
sed -n 's/.*\[\([^]]*\)\].*/\1/p' /var/log/apache2/access.log
# Extract URLs (between quotes after method)
sed -n 's/.*"\(GET\|POST\) \([^ ]*\).*/\2/p' /var/log/apache2/access.log
Filter Log Levels
# Show only ERROR and CRITICAL lines
sed -n '/\(ERROR\|CRITICAL\)/p' /var/log/application.log
# Remove DEBUG lines for cleaner output
sed '/DEBUG/d' /var/log/application.log
# Show context: 2 lines before and after ERROR
sed -n '/ERROR/{x;p;x;p;n;p;n;p}' /var/log/application.log
Anonymize Logs
# Replace IP addresses with [REDACTED]
sed 's/[0-9]\{1,3\}\.[0-9]\{1,3\}\.[0-9]\{1,3\}\.[0-9]\{1,3\}/[REDACTED]/g' access.log
# Replace email addresses
sed 's/[A-Za-z0-9._%+-]*@[A-Za-z0-9.-]*\.[A-Za-z]\{2,\}/[EMAIL]/g' file
# Replace credit card numbers (basic pattern)
sed 's/[0-9]\{4\}[- ]\?[0-9]\{4\}[- ]\?[0-9]\{4\}[- ]\?[0-9]\{4\}/[CC-REDACTED]/g' file
Certificate and Key Operations
Extract Certificate from Combined File
# Extract just the certificate
sed -n '/-----BEGIN CERTIFICATE-----/,/-----END CERTIFICATE-----/p' combined.pem > cert.pem
# Extract just the private key
sed -n '/-----BEGIN.*PRIVATE KEY-----/,/-----END.*PRIVATE KEY-----/p' combined.pem > key.pem
# Extract CA chain (all certs except first)
sed -n '/-----BEGIN CERTIFICATE-----/,/-----END CERTIFICATE-----/{
1,/-----END CERTIFICATE-----/d
p
}' fullchain.pem > chain.pem
JSON/YAML Manipulation
Systemd and Service Files
Modify Unit File
# Change ExecStart
sudo sed -i 's|^ExecStart=.*|ExecStart=/usr/local/bin/myapp --config /etc/myapp/prod.conf|' /etc/systemd/system/myapp.service
# Add After= dependency
sudo sed -i '/^\[Unit\]/a After=network-online.target' /etc/systemd/system/myapp.service
# Change restart policy
sudo sed -i 's/^Restart=.*/Restart=always/' /etc/systemd/system/myapp.service
Bulk File Operations
Advanced Techniques
Branching and Labels
# Syntax:
# :label - Define label
# b label - Branch to label
# t label - Branch if substitution succeeded
# T label - Branch if substitution failed (GNU extension)
# Loop until no more changes
sed ':loop; s/ / /g; t loop' file
# Replaces multiple spaces with single space
# Conditional processing
sed '/pattern/{
s/old/new/
t done
s/fallback/replacement/
:done
}' file
Multi-Line Pattern Matching
# N command: Append next line to pattern space
# Match pattern spanning two lines
sed 'N; s/line1\nline2/replacement/' file
# Join continuation lines (ending with \)
sed ':a; /\\$/{N; s/\\\n//; ta}' file
# Delete multi-line block
sed '/START/,/END/d' file
In-Place Editing with Atomic Safety
# Problem: sed -i can corrupt files on crash
# Safer: Use temporary file
sed 's/old/new/g' file > file.tmp && mv file.tmp file
# With backup
cp file file.bak && sed 's/old/new/g' file.bak > file
# Atomic with sponge (from moreutils)
sed 's/old/new/g' file | sponge file
Script Files
Create reusable sed scripts:
hardening.sed
# SSH Hardening Script
# Usage: sed -f hardening.sed /etc/ssh/sshd_config
# Disable password auth
s/^#\?PasswordAuthentication.*/PasswordAuthentication no/
# Disable root login
s/^#\?PermitRootLogin.*/PermitRootLogin no/
# Use strong ciphers only
/^#\?Ciphers/c\
Ciphers aes256-gcm@openssh.com,aes128-gcm@openssh.com
# Set idle timeout
/^#\?ClientAliveInterval/c\
ClientAliveInterval 300
# Apply script
sudo sed -f hardening.sed -i.bak /etc/ssh/sshd_config
Debugging sed
Show What’s Happening
# Mark all substitutions with brackets
sed 's/pattern/[MATCH: &]/g' file
# Print line numbers of matches
sed -n '/pattern/{=;p}' file
# Verbose: Print before and after each command (GNU sed)
sed --debug 's/old/new/g' file 2>&1 | head -50
Common Pitfalls
1. Greedy Matching
# Problem: .* is greedy
echo "start middle end" | sed 's/start.*end/REPLACED/'
# Matches: "start middle end" (entire thing)
# Solution: Use negated character class
echo "<tag>content</tag>" | sed 's/<[^>]*>/[TAG]/g'
# Output: [TAG]content[TAG]
2. Missing -i Backup
# Dangerous
sed -i 's/old/new/g' /etc/important.conf
# Safe
sed -i.bak 's/old/new/g' /etc/important.conf
3. Unescaped Special Characters
# WRONG
sed 's/192.168.1.1/10.0.0.1/g' # Matches 192x168y1z1
# RIGHT
sed 's/192\.168\.1\.1/10.0.0.1/g'
4. Delimiter Conflicts
# WRONG (path contains /)
sed 's//old/path//new/path/g'
# RIGHT (use different delimiter)
sed 's|/old/path|/new/path|g'
# WRONG (pattern contains / like vim search patterns)
sed 's/{pattern}/\\{pattern}/g' file # Error: unknown option to 's'
# RIGHT (use | when pattern has /)
sed 's|{pattern}|\\{pattern}|g' file # Works!
# REAL-WORLD: Escape AsciiDoc attribute placeholders
# Patterns like /{pattern}, ?{pattern}, `f{char}` contain /
sed -i 's|{char}|\\{char}|g' shortcuts.adoc
sed -i 's|{motion}|\\{motion}|g' shortcuts.adoc
Performance Considerations
Quick Reference Card
╔══════════════════════════════════════════════════════════════════════════════╗ ║ SED QUICK REFERENCE ║ ╠══════════════════════════════════════════════════════════════════════════════╣ ║ ║ ║ PREVIEW/APPLY PATTERN (Golden Rule) ║ ║ ──────────────────────────────────────────────────────────────────────── ║ ║ sed -n 's/old/new/gp' file Preview changes ║ ║ sed -i 's/old/new/g' file Apply changes ║ ║ sed -i.bak 's/old/new/g' file Apply with backup ║ ║ ║ ║ COMMANDS ║ ║ ──────────────────────────────────────────────────────────────────────── ║ ║ s/old/new/g Substitute (g=global) ║ ║ d Delete line ║ ║ p Print line ║ ║ i\text Insert before ║ ║ a\text Append after ║ ║ c\text Change (replace) line ║ ║ q Quit ║ ║ y/abc/xyz/ Transform characters ║ ║ ║ ║ ADDRESSES ║ ║ ──────────────────────────────────────────────────────────────────────── ║ ║ 5 Line 5 ║ ║ 5,10 Lines 5-10 ║ ║ 5,$ Line 5 to end ║ ║ /pattern/ Lines matching pattern ║ ║ /start/,/end/ Range between patterns ║ ║ 1~2 Every 2nd line from 1 ║ ║ ║ ║ FLAGS ║ ║ ──────────────────────────────────────────────────────────────────────── ║ ║ g Global (all occurrences) ║ ║ p Print ║ ║ i Case-insensitive ║ ║ 2 Only 2nd occurrence ║ ║ ║ ║ ESCAPING (BRE) ║ ║ ──────────────────────────────────────────────────────────────────────── ║ ║ \. Literal dot \* Literal asterisk ║ ║ \[ Literal bracket \\ Literal backslash ║ ║ \$ Literal dollar \/ Literal slash (or use |#@ delimiter) ║ ║ ║ ║ HOLD BUFFER ║ ║ ──────────────────────────────────────────────────────────────────────── ║ ║ h Copy pattern→hold H Append pattern→hold ║ ║ g Copy hold→pattern G Append hold→pattern ║ ║ x Exchange pattern↔hold ║ ║ ║ ║ REGEX ║ ║ ──────────────────────────────────────────────────────────────────────── ║ ║ . Any character ^ Start of line ║ ║ * Zero or more $ End of line ║ ║ [abc] Character class [^ab] Negated class ║ ║ \(\) Capture group \1 Backreference ║ ║ & Entire match ║ ║ ║ ║ COMMON OPERATIONS ║ ║ ──────────────────────────────────────────────────────────────────────── ║ ║ sed '/^$/d' Delete empty lines ║ ║ sed '/^#/d' Delete comment lines ║ ║ sed 's/^/#/' Comment out line ║ ║ sed 's/^#//' Uncomment line ║ ║ sed -n '10,20p' Print lines 10-20 ║ ║ sed 's/ */ /g' Collapse whitespace ║ ║ sed 'N;s/\n/ /' Join every 2 lines ║ ║ sed '1!G;h;$!d' Reverse lines (tac) ║ ║ ║ ╚══════════════════════════════════════════════════════════════════════════════╝