Traps
macOS vs GNU sed — in-place edit syntax
# WRONG — macOS requires explicit empty string for no backup
sed -i 's/old/new/' /tmp/file.txt # Works on GNU sed (Linux)
# CORRECT — portable across macOS and GNU
sed -i'' 's/old/new/' /tmp/file.txt # macOS requires -i ''
sed -i.bak 's/old/new/' /tmp/file.txt # Safe on both — always creates backup
Greedy .* matching — captures too much
# WRONG — greedy .* eats through the first bracket pair
echo '[ERROR] [2026-04-11] disk full' | sed 's/\[.*\]/REDACTED/'
# Output: REDACTED disk full (both bracket pairs consumed)
# CORRECT — use negated character class
echo '[ERROR] [2026-04-11] disk full' | sed 's/\[[^]]*\]/REDACTED/'
# Output: REDACTED [2026-04-11] disk full (first pair only)
Slash in pattern — use alternate delimiter
# WRONG — escaping slashes is unreadable
echo '/usr/local/bin/python3' | sed 's/\/usr\/local\/bin/\/opt\/bin/'
# CORRECT — use | or # as delimiter
echo '/usr/local/bin/python3' | sed 's|/usr/local/bin|/opt/bin|'
echo '/usr/local/bin/python3' | sed 's#/usr/local/bin#/opt/bin#'
Newline in replacement — requires literal newline or escape
# WRONG — \n in replacement does not work in all sed versions
echo 'a;b;c' | sed 's/;/\n/g'
# CORRECT — use escaped literal newline (works everywhere)
echo 'a;b;c' | sed 's/;/\
/g'
Single quotes inside sed expression
# WRONG — cannot nest single quotes
echo "it's here" | sed 's/it's/it was/'
# CORRECT — end quote, escape, restart quote
echo "it's here" | sed 's/it'\''s/it was/'
# CORRECT — use double quotes (watch for shell expansion)
echo "it's here" | sed "s/it's/it was/"
BRE vs ERE — capture group syntax differs
# BRE (default) — escape parentheses
echo 'port=443' | sed 's/port=\([0-9]*\)/PORT:\1/'
# ERE (-E flag) — bare parentheses
echo 'port=443' | sed -E 's/port=([0-9]+)/PORT:\1/'
BRE vs ERE — quantifiers differ
# WRONG — + is literal in BRE
echo 'aaa' | sed 's/a+/X/'
# Output: aaa (no match — + is literal)
# CORRECT — use -E for extended regex
echo 'aaa' | sed -E 's/a+/X/'
# Output: X
Hold buffer — confusing multi-line state
# Swap adjacent lines using hold buffer — easy to get wrong
cat <<'EOF' > /tmp/pairs.txt
key1
value1
key2
value2
EOF
# h = copy to hold, n = next line, G = append hold
# This reverses each pair: value first, then key
sed 'h;n;G' /tmp/pairs.txt
Empty regex reuses previous pattern — surprising behavior
# The empty // reuses the last regex
echo 'hello world' | sed -e 's/hello/goodbye/' -e 's//farewell/'
# Second s// reuses /hello/ — but it already changed, so no match
# This is confusing — always be explicit
echo 'hello world' | sed -e 's/hello/goodbye/' -e 's/goodbye/farewell/'