sed Cheatsheet

Basics and Invocation

Use this sed reference while you build software engineering projects, review code, or refresh the syntax you reach for most.

The execution model

sed reads one line at a time into the pattern space, runs the whole script against it, prints the pattern space, and repeats. That cycle explains almost every surprising behavior.

read line -> strip trailing newline -> run every command in order
          -> auto-print pattern space (unless -n) -> next line
sed 's/old/new/' file            # substitute, print everything
sed -n '/error/p' file           # print only what you ask for
sed -n '5,10p' file              # a line range
sed '/^#/d' file                 # delete comment lines
sed -e 's/a/b/' -e 's/c/d/' file # two commands, applied in order
sed -f script.sed file           # commands from a file

The hold space is a second buffer that survives between cycles. It starts empty and only the h H g G x commands touch it.

Command-line options

OptionMeaningPortable?
-nno auto-print, you drive output with pPOSIX
-e scriptappend a script fragment, repeatablePOSIX
-f fileread a script from a file, repeatablePOSIX
-i[ext]edit in place, and the syntax differs by platformneither, see below
-Eextended regex (ERE)POSIX since 2018, works in both
-rERE, GNU spelling. macOS documents it as an alias for -Eavoid, use -E
-streat each file separately, so $ is per fileGNU only
-zrecords are NUL-separated, not newlineGNU only
-uunbuffered outputboth, not POSIX
-lBSD: line-buffered output. GNU: -l N sets the l wrap widthsame letter, different meaning
--posixdisable GNU extensionsGNU only
--debugdump the parsed program and each cycleGNU only
--help --versioninformationGNU only (BSD errors)
sed --version   # GNU prints "sed (GNU sed) 4.10", BSD prints a usage error

That error is the fastest way to tell which sed you have.

The one gotcha that breaks every script

-i takes a mandatory backup suffix on BSD/macOS and an optional attached one on GNU. There is no spelling that means "in place, no backup" on both.

sed -i    's/a/b/' f    # GNU: edits f.  BSD: reads "s/a/b/" as the suffix, then
                        #      tries to run "f" as the script and fails
sed -i '' 's/a/b/' f    # BSD: edits f.  GNU: reads "" as the script, then
                        #      tries to open "s/a/b/" as a file and fails
sed -i'' -e 's/a/b/' f  # GNU: fine.     BSD: writes a backup called f-e

Pick one of these instead:

sed -i.bak 's/a/b/' f && rm -f f.bak     # works on both
perl -i -pe 's/a/b/' f                   # works everywhere, no suffix dance
sed 's/a/b/' f > f.tmp && mv f.tmp f     # explicit, and you can diff first
gsed -i 's/a/b/' f                       # brew install gnu-sed, then use gsed

Regex flavor

sed uses BRE by default and ERE with -E. Perl syntax is not available in either.

ConstructBREERE (-E)
group\(...\)(...)
alternation| GNU only|
one or more\+ GNU only+
zero or one\? GNU only?
interval\{n,m\}{n,m}
backreference in the pattern\1\1 GNU only
\w \s \b \< \>GNU onlyGNU only
[[:alpha:]] [[:digit:]]✓✓
printf 'aaa\n' | sed    's/a\+/X/'   # GNU: X.  BSD: unchanged, \+ is literal
printf 'aaa\n' | sed -E 's/a+/X/'    # X on both
printf 'abab\n' | sed -E 's/(ab)\1/X/'   # GNU: X.  BSD: unchanged
printf 'abab\n' | sed    's/\(ab\)\1/X/' # X on both
printf 'a b\n' | sed 's/[[:space:]]/_/'  # a_b on both

Use -E and the POSIX character classes and you will write regexes that behave the same on a Mac and in CI. Every \w, \s, \+, and \| is a portability bug waiting for a Linux runner.

Exit status and errors

StatusMeaning
0success
1bad usage or a bad script. BSD also uses 1 for an unreadable file
2GNU only: a file could not be read (4 for an I/O error)
NGNU only: whatever qN or QN set
# GNU only: use sed as a test, exit 1 when the pattern is absent
gsed -n '/needle/{s//&/;q0}; $q1' haystack.txt

# portable: let grep do the testing
grep -q needle haystack.txt

sed does not report "no substitutions made" through its status, on either platform. A s/// that matched nothing still exits 0.