Back to Blog

Zsh Tutorial: sed Substitution, Patterns & Practical Commands #18

Sandy LaneSandy Lane

Video: Zsh Tutorial: sed Substitution, Patterns & Practical Commands #18 by Taught by Celeste AI - AI Coding Coach

Take the quiz on the full lesson page
Test what you've read · interactive walkthrough

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/). d delete, p print, i insert before, a append 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 replacesed 's/a/b/g'. Easy.
  • Multi-line patterns — sed gets hard. Use perl or awk.
  • Complex transformationsawk (next lesson).
  • Quick interactive editsnvim with 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.

Ready? Take the quiz on the full lesson page →
Test what you've learned. Watch the lesson and try the interactive quiz on the same page.