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.
| Command | Effect | Read as |
|---|---|---|
h | hold = pattern | hold, overwrite |
H | hold = hold + \n + pattern | Hold, append |
g | pattern = hold | get, overwrite |
G | pattern = pattern + \n + hold | Get, append |
x | swap the two | exchange |
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
| Line | 1!G | h | hold now |
|---|---|---|---|
one | skipped, line 1 | hold = one | one |
two | pattern = two\none | hold = that | two\none |
three | pattern = three\ntwo\none | hold = that | three\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
| Command | Reads a new line | Auto-prints first |
|---|---|---|
n | yes, replaces the pattern space | yes, unless -n |
N | yes, appends with \n | no |
D | only if nothing is left in the buffer | no |
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.