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
|
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.
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#
| Command | Effect |
|---|
s/old/new/ | Replace first match |
s/old/new/g | Replace all matches |
s/old/new/i | Case-insensitive |
d | Delete line |
p | Print line |
i\text | Insert before |
a\text | Append after |
-n | Suppress default output |
-i | Edit in place |
-E | Extended 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.