You need to change a config value across 50 files. You could open each one, or:

1
sed -i 's/old_value/new_value/g' *.conf

Done. sed is the stream editor — it transforms text as it flows through. Master it, and you’ll never manually edit repetitive files again.

The Basics

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
# Replace first occurrence per line
echo "hello hello" | sed 's/hello/hi/'
# hi hello

# Replace all occurrences (g = global)
echo "hello hello" | sed 's/hello/hi/g'
# hi hi

# Replace in file (print to stdout)
sed 's/foo/bar/g' file.txt

# Replace in place (-i)
sed -i 's/foo/bar/g' file.txt

# Backup before in-place edit
sed -i.bak 's/foo/bar/g' file.txt

The -i flag is powerful and dangerous. Always test without it first.

Address Patterns

sed can target specific lines:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
# Line 5 only
sed '5s/foo/bar/' file.txt

# Lines 5-10
sed '5,10s/foo/bar/g' file.txt

# Last line
sed '$s/foo/bar/' file.txt

# Lines matching pattern
sed '/ERROR/s/foo/bar/g' file.txt

# Lines NOT matching pattern
sed '/DEBUG/!s/foo/bar/g' file.txt

Deleting Lines

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
# Delete line 3
sed '3d' file.txt

# Delete lines 5-10
sed '5,10d' file.txt

# Delete lines matching pattern
sed '/DEBUG/d' file.txt

# Delete empty lines
sed '/^$/d' file.txt

# Delete lines starting with #
sed '/^#/d' file.txt

# Keep only lines matching pattern (delete non-matches)
sed '/ERROR/!d' file.txt  # Same as grep ERROR

Inserting and Appending

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
# Insert before line 3
sed '3i\New line here' file.txt

# Append after line 3
sed '3a\New line here' file.txt

# Insert before pattern match
sed '/MARKER/i\Inserted before marker' file.txt

# Append after pattern match
sed '/MARKER/a\Inserted after marker' file.txt

Multiple Commands

1
2
3
4
5
6
7
8
# Semicolon-separated
sed 's/foo/bar/g; s/baz/qux/g' file.txt

# Using -e flag
sed -e 's/foo/bar/g' -e 's/baz/qux/g' file.txt

# From a file
sed -f commands.sed file.txt

Regex Power

sed uses basic regex by default. Use -E for extended regex:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
# Basic regex
sed 's/[0-9][0-9]*/NUMBER/g' file.txt

# Extended regex (-E)
sed -E 's/[0-9]+/NUMBER/g' file.txt

# Capture groups
sed -E 's/([a-z]+)@([a-z]+)/\2:\1/g' emails.txt
# user@domain becomes domain:user

# Word boundaries
sed -E 's/\bfoo\b/bar/g' file.txt

Practical Patterns

Config File Editing

1
2
3
4
5
6
7
8
# Update a setting
sed -i 's/^max_connections=.*/max_connections=200/' config.ini

# Comment out a line
sed -i 's/^dangerous_option/#&/' config.ini

# Uncomment a line
sed -i 's/^#\(wanted_option\)/\1/' config.ini

Log Processing

1
2
3
4
5
6
7
8
# Extract timestamps
sed -E 's/.*\[([0-9]{4}-[0-9]{2}-[0-9]{2})\].*/\1/' access.log

# Remove ANSI colors
sed 's/\x1b\[[0-9;]*m//g' colored_output.txt

# Mask sensitive data
sed -E 's/(password=)[^ ]*/\1*****/g' log.txt

Code Modifications

1
2
3
4
5
6
7
8
# Add import at top of Python files
sed -i '1i\import logging' *.py

# Update version numbers
sed -i 's/version = "1.0.0"/version = "1.1.0"/' setup.py

# Fix trailing whitespace
sed -i 's/[[:space:]]*$//' *.py

Data Transformation

1
2
3
4
5
6
7
8
# CSV: swap columns 1 and 2
sed -E 's/^([^,]*),([^,]*)/\2,\1/' data.csv

# Add quotes around fields
sed 's/\([^,]*\)/"\1"/g' data.csv

# Convert tabs to commas
sed 's/\t/,/g' data.tsv

Working with Ranges

1
2
3
4
5
6
7
8
# Print lines between patterns (inclusive)
sed -n '/START/,/END/p' file.txt

# Delete lines between patterns
sed '/START/,/END/d' file.txt

# Replace only within range
sed '/START/,/END/s/foo/bar/g' file.txt

Hold Space (Advanced)

sed has a secondary buffer called the hold space:

1
2
3
4
5
6
7
8
# Reverse lines (like tac)
sed '1!G;h;$!d' file.txt

# Double-space a file
sed 'G' file.txt

# Number lines
sed = file.txt | sed 'N;s/\n/\t/'

These are rarely needed but powerful for complex transformations.

Combining with Other Tools

1
2
3
4
5
6
7
8
# Find and replace in specific files
find . -name "*.py" -exec sed -i 's/old/new/g' {} +

# Process output of other commands
kubectl get pods | sed -n '2,$p'  # Skip header

# Chain with awk
cat data.txt | sed 's/,/\t/g' | awk '{print $2}'

Common Pitfalls

Delimiter conflicts:

1
2
3
4
5
6
# Bad: slashes conflict with path
sed 's/\/usr\/local/\/opt/g'

# Good: use different delimiter
sed 's|/usr/local|/opt|g'
sed 's#/usr/local#/opt#g'

Greedy matching:

1
2
3
4
5
6
7
# Matches too much
echo "<a>text</a>" | sed 's/<.*>//'
# (empty - matched entire string)

# Non-greedy workaround
echo "<a>text</a>" | sed 's/<[^>]*>//g'
# text

In-place on symlinks:

1
2
3
4
5
# May break symlink! Creates new file
sed -i 's/foo/bar/' symlink.txt

# Safer: check first
[[ -L file.txt ]] && echo "Warning: symlink"

macOS vs Linux

macOS sed requires a backup extension with -i:

1
2
3
4
5
6
7
8
# Linux
sed -i 's/foo/bar/' file.txt

# macOS
sed -i '' 's/foo/bar/' file.txt

# Cross-platform
sed -i.bak 's/foo/bar/' file.txt && rm file.txt.bak

Quick Reference

CommandEffect
s/old/new/Replace first match
s/old/new/gReplace all matches
s/old/new/iCase-insensitive
dDelete line
pPrint line
i\textInsert before
a\textAppend after
-nSuppress default output
-iEdit in place
-EExtended regex

sed is surgical text editing. For anything more complex, reach for awk or Python. But for quick find-and-replace across files? sed is unbeatable.


Computing Arts is CLI craft for the working developer. More at computingarts.com.