SED has two buffers: the pattern space (the working area, reset each cycle) and the hold space (a persistent buffer that survives across lines). Most people only know the pattern space. The hold space is the key to SED's most powerful techniques — reversing files, collecting context, deferring output, and implementing stateful transformations.
1
Hold space commands — full reference
| Command | Action | Effect |
|---|---|---|
| h | copy pattern → hold | Overwrites hold with current pattern space |
| H | append pattern → hold | Appends \n + pattern space to hold space |
| g | copy hold → pattern | Overwrites pattern space with hold space |
| G | append hold → pattern | Appends \n + hold space to pattern space |
| x | exchange pattern ↔ hold | Swaps pattern and hold space contents |
| l | list pattern space | Prints pattern space with non-printables visible |
| = | print line number | Prints current line number to stdout |
2
Classic hold space patterns
BASH
# ── Reverse a file (tac equivalent) ──────────────────────
sed -n '1!G; h; $p'
# Line 1: skip G (no previous), h copies to hold
# Line 2+: G appends hold to pattern (so pattern=new\nold...), h saves
# Last line: p prints everything accumulated in reverse
# ── Print last line first ─────────────────────────────────
sed -n '${p; q}; H; 1h'
# Save all lines in hold; at EOF print last line only
# ── Double-space a file ───────────────────────────────────
sed 'G'
# G appends hold (initially empty = "") to pattern, adding a blank line
# ── Print context: 2 lines before and after a match ───────
sed -n '/ERROR/{
x; p # print previous line (from hold)
x; p # print ERROR line (now in pattern)
n; p # read and print next line
}' app.log
# ── Accumulate all lines and process in END ───────────────
sed -n 'H; ${ g; s/\n/,/g; s/^,//; p }'
# Accumulate everything into hold; at end: copy to pattern,
# join with commas, remove leading comma, print
# Input: a\nb\nc → Output: a,b,c
# ── Keep previous line in hold — detect consecutive matches ─
sed '/ERROR/{
x # swap: hold now has ERROR, pattern has prev
/ERROR/{ H; x; p } # if prev was also ERROR: consecutive!
x # restore pattern = current ERROR line
h # save current line to hold
}' app.log
3
Collecting output and deferred printing
BASH
# ── Collect matching lines, print all at end ──────────────
sed -n '/ERROR/{H}; ${ x; s/^\n//; p }' app.log
# H appends each ERROR line to hold
# At EOF: copy hold to pattern, strip leading newline, print all
# ── Print section header before its content ──────────────
# Print [section] header only when we know it has content
sed -n '/^\[/{
h # save section header in hold
d # suppress it for now
}
/^[^[]/{
x; /./p # if there is a saved header, print it
x; p # then print the content line
}' config.ini
# ── Extract first and last line of each paragraph ─────────
sed -n '/^$/{
x # swap: hold = last line of paragraph
p # print it
s/.*// # clear pattern space
x # clear hold
d
}
1h # first line of para: save to hold
1!{
x; p # print saved first line
x; h # save current as new "last line"
}
${
x; p # at EOF, print last saved line
}' paragraphs.txt
4
SED labels and branching
BASH
# b label — unconditional branch to :label
# t label — branch to :label if substitution succeeded since last line/t
# :label — define a branch target
# ── Loop: keep substituting until no more matches ─────────
sed ':loop; s/ / /; t loop' # collapse all double-spaces
# s/ / /: replace one double-space
# t loop: if it succeeded, repeat — until no double-spaces remain
# ── Join continuation lines with loop ────────────────────
sed ':a; /\\$/{N; s/\\\n//; ta}'
# :a label; if line ends in \, append next, remove \+newline, loop
# ── Skip processing on comment lines ─────────────────────
sed '/^#/b; s/old/new/g; s/foo/bar/g'
# b without a label skips to end of script (prints and next cycle)
# ── Conditional processing ────────────────────────────────
sed '/ERROR/{
s/ERROR/CRITICAL/
b done # skip further processing
}
/WARN/{
s/WARN/WARNING/
}
:done'
# ── Complete: reverse a file with labels ─────────────────
sed -n '1!G; h; $p' file.txt # elegant 3-command reverse
tac file.txt # or just use tac if available
vriddh@prod-01:~/scripts$printf 'a\nb\nc\nd\n' | sed -n '1!G; h; $p'
d
c
b
a
vriddh@prod-01:~/scripts$printf 'x\ny\nz\n' | sed -n 'H; ${ x; s/\n/,/g; s/^,//; p }'
x,y,z
vriddh@prod-01:~/scripts$echo "a b c d" | sed ':l; s/ / /; tl'
a b c d
█
Hold space starts empty — At the start of processing, the hold space contains a single empty string (not truly empty). This is why
G on line 1 (to double-space) adds a blank line — it appends \n + empty hold = just a newline. This also means the reverse idiom 1!G skips line 1 to avoid prepending a spurious newline.✔ Hold space mastery — Use
h/H to save the current line for later. Use g/G to restore it. Use x when you need to swap and keep both lines simultaneously. Use H; ${ x; ...; p } to collect all lines and process at EOF. Use :label; ...; t label for loops that iterate until no substitution occurs. When SED hold-space logic gets complex — switch to AWK.