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
| Option | GNU | BSD / macOS |
|---|---|---|
-n -e -f -E -u | ✓ | ✓ |
-r | ✓ | ✓, an alias for -E |
-i | suffix attached, optional | suffix 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 width | line-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.
| Construct | GNU | BSD | Portable 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 -H | negated 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
-Eand 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
| Feature | GNU | BSD |
|---|---|---|
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
| Feature | GNU | BSD |
|---|---|---|
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-ASCII | escapes both | prints 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.