Zsh Tutorial: sed Substitution, Patterns & Practical Commands #18
Video: Zsh Tutorial: sed Substitution, Patterns & Practical Commands #18 by Taught by Celeste AI - AI Coding Coach
Zsh Lesson 18: sed — Stream Editor for Substitution and Patterns
sed 's/old/new/'first-occurrence replace,'s/old/new/g'global.sed -i ''in-place edit (macOS);sed -i(Linux). Address by line (3), range (2,5), or pattern (/foo/).ddelete,pprint,iinsert before,aappend after.
sed (stream editor) processes text line by line. The most common use is search-and-replace; the second is delete/transform.
Basic substitution
echo "Hello World" | sed 's/Hello/Hi/'
# Hi World
s/old/new/ substitutes the first occurrence per line.
For all occurrences: append g (global):
echo "abc abc abc" | sed 's/a/X/g'
# Xbc Xbc Xbc
For case-insensitive: I flag:
echo "Hello hello HELLO" | sed 's/hello/hi/Ig'
# hi hi hi
On a file
sed 's/Hello/Hi/' greetings.txt
Prints the result to stdout. Doesn't modify the file by default.
Different delimiter
echo "/usr/local/bin" | sed 's|/usr/local|/opt|'
# /opt/bin
The delimiter doesn't have to be /. Useful when the text contains slashes (paths). Common alternatives: |, #, ,, :.
In-place edit: -i
# macOS / BSD
sed -i '' 's/old/new/g' file.txt
# Linux / GNU
sed -i 's/old/new/g' file.txt
macOS sed requires an empty argument after -i. GNU sed doesn't. This is the #1 portability headache.
For cross-platform scripts:
if [[ "$(uname)" == "Darwin" ]]; then
sed -i '' 's/old/new/g' file.txt
else
sed -i 's/old/new/g' file.txt
fi
Or brew install gnu-sed and use gsed. Or write to a temp file and rename.
For backup-on-edit:
sed -i '.bak' 's/old/new/g' file.txt # macOS: creates file.txt.bak
sed -i.bak 's/old/new/g' file.txt # GNU: creates file.txt.bak
Address: which lines to operate on
sed -n '3p' file.txt # print line 3 (with -n: suppress default print)
sed -n '2,5p' file.txt # lines 2 through 5
sed -n '$p' file.txt # last line ($ = end)
sed -n '/error/p' file.txt # lines matching /error/
sed -n '/start/,/end/p' file.txt # range from /start/ to /end/
sed '/^#/d' file.txt # delete comment lines
sed '5,7s/old/new/' file.txt # substitute only lines 5-7
Without -n, sed prints every line by default. -n suppresses; then only p (or other print commands) emit.
Negation: !
sed -n '/^#/!p' file.txt # print lines NOT starting with #
sed -n '5,10!p' file.txt # print all lines except 5-10
! after the address inverts.
Common commands
| Command | Action |
|---|---|
s/old/new/g |
substitute |
d |
delete line |
p |
print line |
i\text |
insert before |
a\text |
append after |
c\text |
change (replace) |
q |
quit immediately |
= |
print line number |
sed '/DEBUG/d' app.log # delete debug lines
sed '/^$/d' file.txt # delete blank lines
sed '/^#/d; /^$/d' file.txt # delete comments AND blanks
Multiple commands separated by ; or with -e:
sed -e 's/INFO/[INFO]/g' -e 's/ERROR/[ERROR]/g' app.log
Insert and append
sed '/ERROR/i\
--- ALERT ---' app.log
Inserts --- ALERT --- before every line matching /ERROR/.
sed '/ERROR/a\
--- END ALERT ---' app.log
Appends after.
The backslash + newline syntax is awkward — many people use \n (GNU only) or single-line forms.
Capture groups
echo "Alice 25" | sed -E 's/([A-Z][a-z]+) ([0-9]+)/\2 \1/'
# 25 Alice
-E enables extended regex (no need for backslash on (, ), +). Capture with (), refer to with \1, \2, etc.
Common patterns
# Strip leading/trailing whitespace
sed 's/^[[:space:]]*//;s/[[:space:]]*$//' file
# Convert tabs to spaces
sed 's/\t/ /g' file
# Add line numbers
sed = file | sed 'N;s/\n/: /'
# Reverse line order (poor-man's `tac`)
sed '1!G;h;$!d' file
# Print every 2nd line
sed -n 'p;n' file
# Replace only on lines NOT matching
sed '/skip/!s/old/new/' file
Some of these are dense — sed has a "small language" for line manipulation.
When to use sed vs alternatives
- Simple replace —
sed 's/a/b/g'. Easy. - Multi-line patterns — sed gets hard. Use
perlorawk. - Complex transformations —
awk(next lesson). - Quick interactive edits —
nvimwith macros.
sed is best for "scripted line edits" and "in a pipeline" usage. For one-off changes to one file, just open the file in your editor.
A real-world script
#!/usr/bin/env zsh
set -euo pipefail
# Bump version in pyproject.toml
OLD="0.1.0"
NEW="0.2.0"
if [[ "$(uname)" == "Darwin" ]]; then
sed -i '' "s/version = \"$OLD\"/version = \"$NEW\"/" pyproject.toml
else
sed -i "s/version = \"$OLD\"/version = \"$NEW\"/" pyproject.toml
fi
echo "Bumped $OLD → $NEW"
Cross-platform sed in a release script.
Common stumbles
macOS -i without ''. GNU works without; macOS errors. Always include '' for macOS scripts.
Delimiter conflict. sed 's/a/b/c' — the trailing c becomes a flag. Pick a different delimiter or escape.
Greedy .*. sed 's/<.*>//' matches the longest run. For non-greedy, sed has no built-in support; switch to perl -pe.
Special characters. & in the replacement means "the matched text." Escape: \&. Same for \1, \2 (backslashes).
Quotes. Double-quote when you need variable expansion: sed "s/old/$NEW/g". Single quotes are literal.
-i and the file. sed -i '' ... needs the empty '' AS A SEPARATE ARG. sed -i'' ... errors on some versions.
ERE vs BRE. Default is BRE (basic regex) — +, ?, (), {} need backslash. -E for ERE. Always -E for sanity.
d doesn't delete in input file. Without -i, sed only prints to stdout. Use -i for in-place.
What's next
Lesson 19: awk. Field-based text processing — extract columns, run conditions, aggregate.
Recap
sed 's/old/new/' first replace; add g for global, I for case-insensitive. -i '' in-place (macOS) or -i (Linux). Addresses: line 3, range 2,5, pattern /regex/, last $. Commands: d delete, p print, i insert, a append. -E for extended regex (cleaner). Use a different delimiter (|, #) when text contains /. For complex transforms, use awk or perl.
Next lesson: awk.