sed Cheatsheet

Substitution

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

Anatomy of s///

[addr]s/regex/replacement/flags
sed 's/cat/dog/' file          # first match on each line
sed 's/cat/dog/g' file         # every match on each line
sed '3s/cat/dog/' file         # only line 3
sed '/^Host/s/cat/dog/' file   # only lines starting with Host
sed -n 's/cat/dog/p' file      # print only the lines that changed

The regex is BRE unless you pass -E. The replacement is not a regex, it is a template with three special characters: &, \N, and \ itself.

Flags

FlagEffectPortable?
greplace every match, not just the firstPOSIX
Nreplace only the Nth match (s/a/X/3)POSIX
Ngreplace the Nth match and all after itGNU only
pprint the pattern space if a replacement happenedPOSIX
w fileappend the changed line to filePOSIX
i or Icase-insensitive matchboth, not POSIX
m or Mmultiline, ^ and $ match at embedded newlines (needs a multi-line pattern space, so pair it with N)GNU only
erun the resulting pattern space as a shell commandGNU only
printf 'a a a a\n' | sed  's/a/X/3'    # a a X a, both platforms
printf 'a a a a\n' | gsed 's/a/X/3g'   # a a X X, GNU only
printf 'a a a a\n' | sed  's/a/X/3g'   # BSD: "more than one number or 'g'"
printf 'ABC\n'     | sed  's/abc/x/I'  # x, both platforms

p combined with -n is the standard "grep and rewrite in one pass":

# print just the version numbers, nothing else
sed -n -E 's/^version[[:space:]]*=[[:space:]]*(.+)$/\1/p' config.ini

The replacement side

TokenMeans
&the entire match
\1 … \9the Nth \(...\) or (...) capture group
\&a literal ampersand
\\a literal backslash
\n \tnewline, tab. Modern GNU and modern macOS sed, not old BSD
\U \L \u \l \Ecase conversion, GNU only
a real newline (backslash-escaped)portable multi-line replacement
# wrap every match in brackets
printf 'cat\n' | sed 's/cat/[&]/'                 # [cat]
# a literal ampersand
printf 'cat\n' | sed 's/cat/A\&B/'                # A&B
# swap two capture groups
printf 'john smith\n' | sed -E 's/(\w+) (\w+)/\2, \1/'   # GNU \w
printf 'john smith\n' | sed -E 's/([a-z]+) ([a-z]+)/\2, \1/'  # portable

GNU case conversion, which has no portable equivalent:

printf 'hello world\n' | gsed -E 's/[a-z]+/\u&/g'   # Hello World
printf 'hello\n'       | gsed 's/.*/\U&/'           # HELLO
printf 'HeLLo\n'       | gsed 's/.*/\L&/'           # hello
printf 'ab cd\n'       | gsed -E 's/(\w+) (\w+)/\U\1\E \2/'  # AB cd

On BSD, uppercase a match with tr or awk instead:

printf 'hello world\n' | awk '{ print toupper($1), $2 }'

Alternate delimiters

Any character can follow s, which is how you avoid escaping every slash in a path.

sed 's|/usr/local|/opt|'  file    # readable
sed 's#/usr/local#/opt#'  file
sed 's,/usr/local,/opt,'  file
sed 's@/usr/local@/opt@'  file

The same works for an address, with a leading backslash to announce the delimiter:

sed -n '\%/usr/local%p' file      # equivalent to /\/usr\/local/p

- The chosen delimiter must be escaped if it appears inside the regex. - Do not pick a regex metacharacter. s.a.b. technically works but . still means "any character" in the pattern, so it matches far more than you meant. - s|...|...| is the right default for anything containing paths or URLs.

Newlines, in and out

The pattern space normally holds one line with no trailing newline, so \n never appears in it and $ matches the end of the line.

# this can never match, there is no newline in the pattern space
sed 's/a\nb/X/' file

# pull the next line in first, then the newline exists
sed 'N;s/a\nb/X/' file

# a portable multi-line replacement: escape a real newline
sed 's/,/\
/g' file

# GNU and modern macOS sed also accept \n on the right
sed 's/,/\n/g' file

\n in the replacement is a modern convenience. It works in GNU sed and in current macOS sed, but older BSD sed emitted a literal n and silently corrupted your data. The escaped-real-newline form above works everywhere.

Substitution gotchas

# greedy: .* runs to the LAST semicolon on the line
printf 'a=1;b=2;c=3\n' | sed 's/=.*;/=X;/'      # a=X;c=3
# non-greedy does not exist, use a negated class
printf 'a=1;b=2;c=3\n' | sed 's/=[^;]*;/=X;/'   # a=X;b=2;c=3

# an empty regex reuses the LAST regex used, not "match nothing"
printf 'cat\n' | sed '/cat/s//dog/'         # dog

# & and \ are special in the replacement even when they arrive from a
# variable, so pre-escape anything you interpolate
val=$(printf '%s' 'A&B' | sed 's/[&\/]/\\&/g')
printf 'cat\n' | sed "s/cat/$val/"          # A&B

# a shell variable containing / breaks the command, change the delimiter
p=/usr/bin; printf 'X\n' | sed "s|X|$p|"    # /usr/bin

- s/// reports nothing when it does not match, and the exit status stays 0. Add -n ... p and check for empty output if you need to know. - g and N count non-overlapping matches left to right. - A zero-length match with g advances one character rather than looping forever: sed 's/x*/-/g' inserts a dash between every character.