sed Cheatsheet

Hold Space

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

Two buffers

sed has exactly two buffers. The pattern space holds the current line and is rebuilt every cycle. The hold space starts empty and survives across cycles. Only five commands move data between them, and none of them takes an argument.

CommandEffectRead as
hhold = patternhold, overwrite
Hhold = hold + \n + patternHold, append
gpattern = holdget, overwrite
Gpattern = pattern + \n + holdGet, append
xswap the twoexchange

Lowercase overwrites, uppercase appends with a newline between. That single rule is most of what you need.

sed 'G' file            # double-space: append an empty hold space
sed 'G;G' file          # triple-space
sed -n 'x;$p' file      # print the second-to-last line
sed -n '1!G;h;$p' file  # reverse the file, like tac

The hold space is empty at startup, which is why plain G appends a blank line rather than nothing. sed -n '1{x;p;}' prints one empty line, proving it.

tac, the canonical example

Walk the cycle by hand once and every other hold-space one-liner becomes readable.

sed -n '1!G;h;$p' file      # or: sed '1!G;h;$!d' file
Line1!Ghhold now
oneskipped, line 1hold = oneone
twopattern = two\nonehold = thattwo\none
threepattern = three\ntwo\nonehold = thatthree\ntwo\none

At $, p prints the accumulated pattern space, newlines and all. The 1! guard exists only so the first line does not pick up the empty hold space as a trailing blank line.

Accumulate, then act at the end

The general shape is H on every line and a ${x; ...} block that processes the whole file at once. Anything that needs to see all the lines together goes here.

# join every line with a separator
sed -n 'H;${x;s/^\n//;s/\n/,/g;p;}' file

# same, with a nicer separator
sed -n 'H;${x;s/^\n//;s/\n/ | /g;p;}' file

# collapse each blank-line-separated paragraph onto one line
sed -e '/./{H;$!d;}' -e 'x;s/\n/ /g;s/^ //;p' -e 'd' file

# print only paragraphs containing a word
sed -e '/./{H;$!d;}' -e 'x;/needle/!d' file

s/^\n// is not optional. The first H appends to an empty hold space, so the result always begins with a newline.

Look back and look ahead

The hold space is the only way to reference a line you already printed. n and N are the way to reference one you have not read yet.

# the line BEFORE each match
sed -n '/three/{x;p;d;};x' file

# the line AFTER each match
sed -n '/three/{n;p;}' file

# the match plus one line either side
sed -n -e '/three/{x;p;x;p;n;p;}' -e 'h' file

# the last two lines
sed -n '$!{h;d;};x;G;p' file
CommandReads a new lineAuto-prints first
nyes, replaces the pattern spaceyes, unless -n
Nyes, appends with \nno
Donly if nothing is left in the bufferno

N on the last line is where GNU and POSIX part ways. GNU prints the pattern space and exits, BSD and macOS sed quit without printing it, silently losing the last line. Guard it with $!N and the behavior is the same everywhere.

Multi-line pattern spaces

Once N or G has put a newline inside the pattern space, ^ and $ still mean "start and end of the whole buffer", not of each embedded line. . does not match the embedded newline either.

# join every pair of lines
sed 'N;s/\n/ /' file

# join ALL lines, loop until the last
sed -e :a -e '$!N;s/\n/,/;ta' file

# match text spanning two lines, sliding so every pair is seen
sed '$!N;s/foo\nbar/FOOBAR/;P;D' file

# GNU M flag: ^ and $ match at embedded newlines
gsed 'N;s/^bar$/X/M' file

To address embedded lines individually you need P and D rather than a regex anchor, or the GNU-only M/m flag on s///.

Deduplicate without sorting

The one hold-space one-liner worth memorizing verbatim, because it is uniq without sort and there is no simpler way in sed.

# remove duplicate lines anywhere in the file, keep first occurrence
sed -n 'G; s/\n/&&/; /^\([ -~]*\n\).*\n\1/d; s/\n//; h; P' file

# remove duplicates only when ADJACENT, like uniq
sed '$!N; /^\(.*\)\n\1$/!P; D' file

# print only the lines that were duplicated
sed -n '$!N; s/^\(.*\)\n\1$/\1/p; D' file

- [ -~] is the printable-ASCII range, used because a plain .* would also match the newline separator and break the backreference. - The whole-file version holds every unique line in memory, so it is O(n²) on line count. Past a few thousand lines use awk '!seen[$0]++' instead. - Reverse the characters in each line with the same machinery: sed '/\n/!G;s/\(.\)\(.*\n\)/&\2\1/;//D;s/.//', which is rev.