sed Cheatsheet

GNU vs BSD

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

Which sed do I have

macOS and the BSDs ship BSD sed. Linux, WSL, and almost every CI image ship GNU sed. A one-liner copied from a blog was almost certainly written for GNU.

sed --version    # GNU: "sed (GNU sed) 4.10". BSD: usage error, exit 1
brew install gnu-sed && gsed --version   # GNU on macOS, as gsed

If you control the environment, install gnu-sed and call gsed. If you do not, the rest of this page is the list of things to avoid.

Options

OptionGNUBSD / macOS
-n -e -f -E -u✓✓
-r✓✓, an alias for -E
-isuffix attached, optionalsuffix separate and required
-I✗✓, one shared line-number space
-s, treat files separately✓✗ (but -i already does it)
-z, NUL-separated records✓✗
-l-l N, the l wrap widthline-buffered output, no argument
-a, delay opening w files✗✓
-H, enhanced regex✗✓
--posix --debug --help --version✓✗, all errors

-l is the trap. sed -l 5 means two unrelated things and neither platform complains in an obvious way.

-i, the biggest one

There is no single spelling of "edit in place, no backup" that works on both.

sed -i    's/a/b/' f     # GNU only
sed -i '' 's/a/b/' f     # BSD only
sed -i.bak 's/a/b/' f && rm -f f.bak    # both
perl -i -pe 's/a/b/' f                  # both, no suffix dance

BSD also has -I, which keeps one line-number space across all the files, so $ matches only the last line of the last file. GNU has no equivalent.

Regex differences

Both default to BRE and both take ERE with -E. The gaps are in what GNU adds to BRE and in the Perl-flavored escapes.

ConstructGNUBSDPortable form
\+ \? | in BRE✓✗, literal-E with + ? |
\1 backreference in an ERE✓✗BRE \(...\) and \1
\w \W \s \S✓✗[[:alnum:]_], [[:space:]]
\b \< \> word boundaries✓✗restructure, or grep -w
\t \n \r in the pattern✓✓ on current macOS$'...' quoting
[[:alpha:]] classes, \xNN✓✓always prefer these
` \ ` and \'` buffer anchors✓✗^ and $
non-greedy +? *?✗only with -Hnegated class [^x]*
printf 'aaa\n'  | sed  's/a\+/X/'      # GNU: X.  BSD: aaa, literal plus
printf 'cat\n'  | sed  's/cat\|dog/X/' # GNU: X.  BSD: cat
printf 'ab1\n'  | sed  's/\w/X/'       # GNU: Xb1. BSD: ab1
printf 'abab\n' | sed -E 's/(ab)\1/X/' # GNU: X.  BSD: abab
printf 'abab\n' | sed  's/\(ab\)\1/X/' # X on both

Pass -E and use POSIX character classes. Every \w, \s, \+, and \| in a BRE is a portability bug waiting for a different runner.

BSD has an escape hatch nobody mentions. -H turns on macOS REG_ENHANCED, which adds most of what GNU has plus non-greedy operators GNU lacks. Being BSD-only it is no more portable than the GNU spellings.

printf 'aaa\n'     | sed -H 's/a\+/X/'      # X
printf 'foo bar\n' | sed -H 's/\bbar\b/X/'  # foo X
printf 'aaa\n'     | sed -H -E 's/a+?/X/'   # Xaa, non-greedy

Substitution flags

FeatureGNUBSD
g p N w file, i/I✓✓
Ng, Nth match and onward✓✗, "more than one number or 'g'"
m / M multiline anchors✓✗
e, run the result as a command✓✗
& and \1-\9 in the replacement✓✓
\n \t in the replacement✓✓ on current macOS
\U \L \u \l \E case conversion✓✗, emits a literal letter
printf 'aaaa\n' | gsed 's/a/X/3g'   # aaXX, GNU only
printf 'ab\n'   | gsed 's/.*/\U&/'  # AB
printf 'ab\n'   | sed  's/.*/\U&/'  # Uab, silently wrong, no error

The case row is the dangerous one, because BSD does not error. It inserts a literal letter and quietly corrupts the data. Portable uppercase is tr 'a-z' 'A-Z' or awk '{ print toupper($0) }'.

Commands and addresses

FeatureGNUBSD
s y p P d D n N h H g G x b t : = l q r w a i c {}✓✓
T, branch if no substitution✓✗
Q, quit without printing✓✗
qN / QN, quit with an exit code✓✗, q takes no argument
R one line per cycle, W first line only✓✗
F file name, z zap, e shell, v version✓✗
first~step addresses✓✗, invalid command code ~
addr,+N and addr,~N✓✗, silently misparsed
0,/re/, so line 1 can end the range✓✗, matches nothing
a text i text c text on one line✓✗, needs a real newline
l N wrap width✓✗, syntax error
l escaping of \\ and non-ASCIIescapes bothprints both raw
seq 1 6 | gsed -n '1~2p'    # 1 3 5, GNU only
seq 1 6 | sed  -n 'p;n'     # 1 3 5, portable
seq 1 6 | sed  -n '2,+2p'   # BSD: prints just 2, no error
printf 'END\nx\n' | sed -n '0,/END/p'    # BSD: nothing, and exit 0

Labels and a/i/c text

Two BSD parsing quirks, not missing features, break the most copied scripts.

1. BSD reads everything after :, b, and t as the label, semicolons and all.

sed ':a;s/x/y/;ta' file            # GNU ok. BSD: unused label 'a;s/x/y/;ta'
sed -e :a -e 's/x/y/' -e ta file   # both

2. BSD a, i, and c need a backslash and then a real newline.

sed '2i hello' file                # GNU only
sed '2i\hello' file                # GNU ok. BSD: extra characters after \
sed -e '2i\' -e 'hello' file       # GNU ok, FAILS on current macOS sed
sed '2i\
hello' file                        # both
sed $'2i\\\nhello' file            # both, one line, bash and zsh

Writing one script for both

# best: require GNU sed and say so
command -v gsed >/dev/null || { echo "brew install gnu-sed" >&2; exit 1; }

# or detect and adapt the -i argument
if sed --version >/dev/null 2>&1; then SEDI=(-i); else SEDI=(-i ''); fi
sed "${SEDI[@]}" -E 's/old/new/g' file

Safe on both: -n -e -f -E, POSIX classes, s/// with g p N w I, & and \1, y, d D p P n N h H g G x, bare q, =, bare l, line and /re/ addresses, addr1,addr2, !, {} with a ; before the brace, labels in their own -e, and a/i/c with a real newline. Everything else needs a check.