sed Stream Editor

Stream editing, substitution patterns, and in-place file modification.

Substitution Basics

# Basic substitution
sed 's/old/new/' file                    # First occurrence per line
sed 's/old/new/g' file                   # All occurrences (global)
sed 's/old/new/2' file                   # Second occurrence only
sed 's/old/new/gi' file                  # Global, case insensitive

# In-place editing
sed -i 's/old/new/g' file                # Edit file directly
sed -i.bak 's/old/new/g' file            # With backup (.bak)

# Alternative delimiters (when pattern contains /)
sed 's|/var/log|/opt/log|g' file         # Pipe delimiter
sed 's#/home/user#/home/admin#g' file    # Hash delimiter
sed 's@http://@https://@g' file          # At sign delimiter

# Escape special characters
sed 's/\./,/g' file                      # Replace literal dot
sed 's/\*/x/g' file                      # Replace literal asterisk
sed 's/\[ERROR\]/[WARN]/g' file          # Replace brackets

# Use regex
sed 's/[0-9]\+/NUM/g' file               # Replace numbers
sed 's/vault-[0-9]*/vault-XX/g' file     # Anonymize host numbers
sed 's/[[:space:]]\+/ /g' file           # Collapse whitespace

# Capture groups (backreferences)
sed 's/\(.*\)@\(.*\)/User: \1, Domain: \2/' file    # Split email
sed 's/^\([^:]*\):/[\1]:/' file                     # Bracket first field
sed 's/\([0-9]\{4\}\)-\([0-9]\{2\}\)-\([0-9]\{2\}\)/\3\/\2\/\1/' file  # Date format

# Extended regex (-E or -r)
sed -E 's/([0-9]+)-([0-9]+)/\2-\1/g' file          # Swap numbers
sed -E 's/vault-([0-9]+)/server-\1/g' file         # Rename pattern

# Infrastructure: Update IP in config
sed -i 's/10\.50\.1\.20/10.50.1.21/g' /etc/hosts

# Infrastructure: Update hostname
sed -i 's/vault-01/vault-02/g' config.yaml

Line Addressing

# By line number
sed -n '5p' file                         # Print line 5 only
sed -n '5,10p' file                      # Print lines 5-10
sed '5d' file                            # Delete line 5
sed '5,10d' file                         # Delete lines 5-10

# From line to end
sed -n '5,$p' file                       # From line 5 to end
sed '5,$d' file                          # Delete from line 5 to end

# First/last line
sed -n '1p' file                         # First line
sed -n '$p' file                         # Last line
sed '1d' file                            # Delete first line
sed '$d' file                            # Delete last line

# Every nth line
sed -n '0~2p' file                       # Even lines
sed -n '1~2p' file                       # Odd lines
sed -n '0~5p' file                       # Every 5th line

# By pattern
sed -n '/ERROR/p' file                   # Lines containing ERROR
sed '/ERROR/d' file                      # Delete lines with ERROR
sed -n '/START/,/END/p' file             # Range between patterns

# Pattern + offset
sed -n '/pattern/,+3p' file              # Pattern line + next 3 lines
sed '/pattern/,+3d' file                 # Delete pattern + next 3 lines

# Negation
sed -n '/ERROR/!p' file                  # Lines NOT containing ERROR
sed '1!d' file                           # Delete all except first line

# Combined addressing
sed -n '5,/pattern/p' file               # From line 5 to pattern
sed '/START/,/END/{s/old/new/g}' file    # Substitute only in range

# Infrastructure: Extract config section
sed -n '/^\[database\]/,/^\[/p' config.ini | head -n -1

# Infrastructure: Skip header
sed '1d' data.csv

# Infrastructure: Get specific line for verification
sed -n '73p' /etc/ssh/sshd_config

Insert, Append, Change

# Insert before line
sed '3i\New line before line 3' file
sed '/pattern/i\New line before pattern' file

# Append after line
sed '3a\New line after line 3' file
sed '/pattern/a\New line after pattern' file

# Change (replace) line
sed '3c\This replaces line 3' file
sed '/pattern/c\This replaces the pattern line' file

# Insert at beginning
sed '1i\Header line' file

# Append at end
sed '$a\Footer line' file

# Multi-line insert (use backslash)
sed '1i\Line 1\
Line 2\
Line 3' file

# Insert with variable
header="# Configuration file"
sed "1i\\$header" file

# Insert blank line
sed '/pattern/a\\' file                  # Blank line after pattern
sed '/pattern/i\\' file                  # Blank line before pattern

# Add line numbers
sed = file | sed 'N;s/\n/\t/'            # Number: content

# Infrastructure: Add comment header to config
sed -i '1i\# Generated by automation - do not edit manually' config.yaml

# Infrastructure: Add DNS entry
sed -i '/^$/i\10.50.1.60 vault-01.inside.domusdigitalis.dev vault-01' /etc/hosts

Delete Operations

# Delete specific lines
sed '5d' file                            # Delete line 5
sed '5,10d' file                         # Delete lines 5-10
sed '1d' file                            # Delete first line
sed '$d' file                            # Delete last line

# Delete by pattern
sed '/pattern/d' file                    # Delete matching lines
sed '/^#/d' file                         # Delete comments
sed '/^$/d' file                         # Delete empty lines
sed '/^[[:space:]]*$/d' file             # Delete blank lines (including whitespace)

# Delete range
sed '/START/,/END/d' file                # Delete between patterns (inclusive)

# Delete except
sed '1,5!d' file                         # Keep only lines 1-5
sed '/keep/!d' file                      # Keep only matching lines

# Delete first/last n lines
sed '1,5d' file                          # Delete first 5 lines
head -n -5 file                          # Alternative: delete last 5 (not sed)

# Delete duplicates (consecutive)
sed '$!N; /^\(.*\)\n\1$/!P; D' file

# Infrastructure: Remove commented lines from config
sed -i '/^#/d; /^$/d' config.conf

# Infrastructure: Clean up hosts file
sed -i '/obsolete-host/d' /etc/hosts

# Infrastructure: Remove sensitive data
sed -i '/password/d; /secret/d; /token/d' log.txt

Multiple Commands

# Semicolon separator
sed 's/old/new/g; s/foo/bar/g' file

# Multiple -e flags
sed -e 's/old/new/g' -e 's/foo/bar/g' file

# Curly braces for grouping
sed '/pattern/{s/old/new/g; s/foo/bar/g}' file

# Script file
cat << 'EOF' > commands.sed
s/old/new/g
s/foo/bar/g
/delete_me/d
EOF
sed -f commands.sed file

# Chain operations
sed '/pattern/{
    s/old/new/g
    s/foo/bar/g
    a\Appended line
}' file

# Process only matching lines
sed '/ERROR/{
    s/ERROR/CRITICAL/
    s/^/>>> /
}' file

# Infrastructure: Multiple host replacements
sed -e 's/vault-01/vault-02/g' \
    -e 's/10.50.1.60/10.50.1.61/g' \
    -e 's/primary/secondary/g' config.yaml

# Infrastructure: Clean and transform config
sed -e '/^#/d' \
    -e '/^$/d' \
    -e 's/[[:space:]]*=[[:space:]]*/=/g' \
    -e 's/"//g' config.conf

Hold Buffer (Advanced)

# Pattern space (default) vs Hold space (secondary buffer)
# h - copy pattern space to hold space
# H - append pattern space to hold space
# g - copy hold space to pattern space
# G - append hold space to pattern space
# x - exchange pattern and hold space

# Reverse lines (like tac)
sed '1!G;h;$!d' file

# Join pairs of lines
sed 'N;s/\n/ /' file

# Join all lines
sed ':a;N;$!ba;s/\n/ /g' file

# Add line after each line
sed 'G' file                             # Add blank line after each

# Print last line only (like tail -1)
sed -n '$p' file

# Print first and last line
sed -n '1p;$p' file

# Swap adjacent lines
sed 'N;s/\(.*\)\n\(.*\)/\2\n\1/' file

# Delete every other line starting with first
sed '1~2d' file

# Collect lines matching pattern
sed -n '/pattern/H;${x;p}' file

# Number lines
sed = file | sed 'N;s/\n/: /'

# Infrastructure: Format multi-line records
sed '/^START/,/^END/{H;/^END/{x;s/\n/ /g;p}}; d' file

Regex Patterns

# Character classes
sed 's/[0-9]\+/NUM/g' file               # One or more digits
sed 's/[a-zA-Z]\+/WORD/g' file           # One or more letters
sed 's/[[:alpha:]]\+/WORD/g' file        # POSIX: letters
sed 's/[[:digit:]]\+/NUM/g' file         # POSIX: digits
sed 's/[[:space:]]\+/ /g' file           # POSIX: whitespace
sed 's/[[:punct:]]/./g' file             # POSIX: punctuation

# Anchors
sed 's/^/PREFIX: /' file                 # Add prefix to each line
sed 's/$/ :SUFFIX/' file                 # Add suffix to each line
sed 's/^[[:space:]]*//' file             # Remove leading whitespace
sed 's/[[:space:]]*$//' file             # Remove trailing whitespace

# Word boundaries (GNU sed)
sed 's/\bword\b/WORD/g' file             # Whole word only
sed 's/\<vault\>/VAULT/g' file           # Word boundary

# Quantifiers
sed 's/a*/X/' file                       # Zero or more 'a'
sed 's/a\+/X/' file                      # One or more 'a'
sed 's/a\?/X/' file                      # Zero or one 'a'
sed 's/a\{3\}/X/' file                   # Exactly 3 'a'
sed 's/a\{2,4\}/X/' file                 # 2 to 4 'a'
sed 's/a\{2,\}/X/' file                  # 2 or more 'a'

# Extended regex (-E)
sed -E 's/[0-9]+/NUM/g' file             # + without backslash
sed -E 's/(foo|bar)/baz/g' file          # Alternation
sed -E 's/([0-9]{1,3}\.){3}[0-9]{1,3}/IP/g' file  # IP pattern

# Greedy vs non-greedy (sed is always greedy)
echo "aaa" | sed 's/a\+/X/'              # X (matches all)
# For non-greedy, use negated character class
echo "<a>b</a>" | sed 's/<[^>]*>/TAG/g'  # TAG b TAG

# Infrastructure: Validate IP format
sed -n '/^[0-9]\{1,3\}\.[0-9]\{1,3\}\.[0-9]\{1,3\}\.[0-9]\{1,3\}$/p' ips.txt

# Infrastructure: Mask sensitive data
sed -E 's/password=.*/password=REDACTED/' config.txt
sed -E 's/([0-9]{4})[0-9]{8}([0-9]{4})/\1********\2/g' file  # Mask card numbers

Print and Format

# Print only (-n suppresses default output)
sed -n 'p' file                          # Same as cat
sed -n '5p' file                         # Print line 5
sed -n '5,10p' file                      # Print lines 5-10
sed -n '/pattern/p' file                 # Print matching lines

# Print with line numbers
sed -n '=' file                          # Line numbers only
sed = file | sed 'N;s/\n/: /'            # Number: content

# Print matched groups (using s command)
sed -n 's/.*\(pattern\).*/\1/p' file     # Extract matched portion

# Double space
sed G file                               # Add blank line after each

# Single space (remove blank lines between)
sed '/^$/d' file

# Trim whitespace
sed 's/^[[:space:]]*//; s/[[:space:]]*$//' file

# Convert to uppercase/lowercase (GNU sed)
sed 's/\(.*\)/\U\1/' file                # Uppercase
sed 's/\(.*\)/\L\1/' file                # Lowercase
sed 's/\<./\u&/g' file                   # Capitalize each word

# Wrap lines at column
sed -E 's/(.{80})/\1\n/g' file           # Wrap at 80 chars

# Infrastructure: Format as table
sed 's/:/\t/g' /etc/passwd | column -t

# Infrastructure: Align output
sed 's/=/\t=\t/' config.conf | column -t -s $'\t'

Infrastructure Patterns

# Update SSH config
sudo sed -i 's/^#\?PermitRootLogin.*/PermitRootLogin no/' /etc/ssh/sshd_config
sudo sed -i 's/^#\?PasswordAuthentication.*/PasswordAuthentication no/' /etc/ssh/sshd_config

# Verify line before change
sed -n '73p' /etc/ssh/sshd_config
# Change specific line
sudo sed -i '73s/^#GSSAPIAuthentication no/GSSAPIAuthentication yes/' /etc/ssh/sshd_config
# Verify after change
sed -n '73p' /etc/ssh/sshd_config

# Update hosts file
sudo sed -i '/old-host/d' /etc/hosts
echo "10.50.1.60 vault-01.inside.domusdigitalis.dev vault-01" | sudo tee -a /etc/hosts

# Comment/uncomment lines
sed -i 's/^dangerous_option/#&/' config.conf          # Comment out
sed -i 's/^#\(wanted_option\)/\1/' config.conf        # Uncomment

# Update version numbers
sed -i 's/version: [0-9]\+\.[0-9]\+\.[0-9]\+/version: 2.0.0/' Chart.yaml

# Replace in systemd unit file
sudo sed -i 's|ExecStart=.*|ExecStart=/usr/bin/myapp --config /etc/myapp/config.yaml|' /etc/systemd/system/myapp.service
sudo systemctl daemon-reload

# Update DNS resolvers
sudo sed -i 's/^nameserver.*/nameserver 10.50.1.90/' /etc/resolv.conf

# Fix file permissions in sudoers
sudo sed -i 's/ALL$/NOPASSWD: ALL/' /etc/sudoers.d/admin

# Update Kubernetes manifest
sed -i 's/replicas: [0-9]\+/replicas: 3/' deployment.yaml

# Replace in ansible inventory
sed -i "s/ansible_host=.*/ansible_host=$NEW_IP/" inventory.ini

# Update certificate paths
sed -i "s|/etc/ssl/certs/old.crt|/etc/ssl/certs/new.crt|g" /etc/nginx/nginx.conf

# Mass update across files (with find)
find /etc/myapp -name "*.conf" -exec sed -i 's/old_setting/new_setting/g' {} \;

# Update YAML value (simple case)
sed -i 's/\(port:\) [0-9]\+/\1 8443/' config.yaml

# Add entry if not exists
grep -q "new_entry" /etc/config || sed -i '$a\new_entry=value' /etc/config

# Remove duplicate empty lines
sed -i '/^$/N;/^\n$/d' file

# Convert Windows to Unix line endings
sed -i 's/\r$//' file

# Backup before bulk edit
for f in *.conf; do
    cp "$f" "$f.bak"
    sed -i 's/old/new/g' "$f"
done

sed Gotchas

# WRONG: In-place without backup can lose data
sed -i 's/old/new/g' important_file      # No backup!

# CORRECT: Always backup when editing important files
sed -i.bak 's/old/new/g' important_file

# WRONG: Delimiter in pattern without escaping
sed 's/path/to/file/new/path/g' file     # Error!

# CORRECT: Use alternative delimiter
sed 's|path/to/file|new/path|g' file

# WRONG: Forgetting -n with p
sed '/pattern/p' file                    # Prints matching lines TWICE

# CORRECT: Use -n to suppress default output
sed -n '/pattern/p' file

# WRONG: Literal interpretation of special chars
sed 's/.*//g' file                       # Deletes everything!

# CORRECT: Escape or be explicit
sed 's/\.\*//g' file                     # Delete literal .*

# WRONG: BRE vs ERE confusion
sed 's/(foo|bar)/baz/g' file             # Literal parentheses!

# CORRECT: Use -E for extended regex
sed -E 's/(foo|bar)/baz/g' file

# WRONG: Expecting in-place to work on symlinks
sed -i 's/old/new/g' /etc/symlink        # Breaks symlink!

# CORRECT: Follow symlink explicitly or edit target
sed -i 's/old/new/g' "$(readlink -f /etc/symlink)"

# WRONG: Multiline matching by default
sed 's/start.*end/MATCH/' file           # Only works on same line

# CORRECT: Use N to read multiple lines
sed 'N;s/start\n.*end/MATCH/' file

# WRONG: Greedy matching when you don't want it
echo "<a>text</a>" | sed 's/<.*>/TAG/'   # <a>text</a> -> TAG

# CORRECT: Use negated character class
echo "<a>text</a>" | sed 's/<[^>]*>/TAG/g'  # <a> -> TAG, </a> -> TAG

# WRONG: Quotes with shell variables
var="value"
sed 's/$var/replacement/' file           # Literal $var

# CORRECT: Use double quotes for expansion
sed "s/$var/replacement/" file

# WRONG: Special chars in replacement
sed "s/old/path/to/new/" file            # Interpreted as delimiters!

# CORRECT: Escape or use different delimiter
sed "s|old|path/to/new|" file

# WRONG: Pattern contains / (common with vim patterns, URLs)
sed 's/{pattern}/\\{pattern}/g' file     # Error: unknown option to 's'
# The /{pattern} is interpreted as: s/{pattern}  /  \\  {pattern}  /g

# CORRECT: Use | delimiter when pattern contains /
sed 's|{pattern}|\\{pattern}|g' file     # Works!

# REAL-WORLD: Escape AsciiDoc attribute placeholders in docs
# Patterns like /{pattern}, ?{pattern}, `f{char}` contain /
sed -i 's|{char}|\\{char}|g' shortcuts.adoc
sed -i 's|{motion}|\\{motion}|g' shortcuts.adoc
sed -i 's|{pattern}|\\{pattern}|g' shortcuts.adoc

# WRONG: Line numbers from grep
grep -n "pattern" file | sed 's/old/new/'  # Includes line number in output!

# CORRECT: Use grep -o or sed alone
sed -n '/pattern/{s/old/new/p}' file

# macOS vs Linux sed differences
# macOS requires argument to -i
sed -i '' 's/old/new/' file              # macOS
sed -i 's/old/new/' file                 # Linux
# For portable scripts:
sed -i.bak 's/old/new/' file && rm -f file.bak