Buffering......

Learning SED

Chapter 6 — The Hold Space

Two Buffers, Not One

Until now, every SED command has worked with a single piece of memory — the pattern space — which holds the current line and is cleared at the end of every cycle. This works perfectly for transformations that only need to look at one line at a time.

But some problems require memory that survives from one line to the next. How do you reverse the lines of a file? How do you print a line only if the next line matches something? How do you collect a group of lines and reassemble them? You cannot solve these with only the pattern space, because it is wiped clean every cycle.

SED's answer is the hold space — a second buffer that persists across cycles. It starts empty and keeps whatever you put in it until you change it or SED finishes. The hold space is never automatically cleared, never automatically printed, and never automatically written. You control it entirely with five commands.

Pattern Space Current line being processed.
Cleared at end of every cycle.
h / H ➜ ⬅ g / G ↕ x
Hold Space Persistent storage between cycles.
Starts empty. Never auto-printed.

The Five Hold Space Commands

Command Full name What it does
h hold (copy) Copy pattern space → hold space (overwrites hold space)
H Hold (append) Append a newline + pattern space → hold space
g get (copy) Copy hold space → pattern space (overwrites pattern space)
G Get (append) Append a newline + hold space → pattern space
x exchange Swap the contents of pattern space and hold space

The capitalisation rule is consistent and worth memorising: lowercase copies (overwriting the destination), uppercase appends (adding to the destination with a separating newline). The direction is always: h/H go to the hold space; g/G come from it; x swaps both ways simultaneously.

Understanding Each Command

h — copy pattern space to hold space

The pattern space is copied into the hold space, replacing whatever was there. Think of it as saving the current line for later.

# Save line 3 for later use
sed '3h' file.txt
# (nothing changes in output — h just saves, it doesn't print anything)

H — append pattern space to hold space

A newline followed by the pattern space is appended to the hold space. The hold space grows line by line. This is how you accumulate multiple lines into a single buffer.

# Accumulate all lines into the hold space, print everything at end
sed -n 'H; ${g; p}' file.txt
# H appends each line; $ triggers g (copy hold→pattern) and p (print)
# Result: the entire file in the pattern space on the last line
# Note: the hold space starts empty so the first H adds a leading newline
H always prepends a newline before appending. Because the hold space starts empty, the very first H in a run produces a leading newline (empty string + newline + first line). This is usually harmless but watch for it in scripts that process the accumulated content. Use 1{h;d}; H instead of plain H to avoid the leading newline (see recipes below).

g — copy hold space to pattern space

The hold space is copied into the pattern space, replacing the current line. The old pattern space content is lost. This is how you retrieve what you saved earlier and do something with it.

# On the last line, replace the pattern space with the hold space contents
sed -n '${ g; p }' file.txt

G — append hold space to pattern space

A newline followed by the hold space is appended to the pattern space. The current line is preserved and the held content follows it. Useful for inserting held content after every line, or for building combined lines.

# Append the hold space (whatever was saved) after every line
sed '1h; G' file.txt
# h saves line 1 into hold; G appends it after every subsequent line

x — exchange pattern space and hold space

The contents of the pattern space and hold space are swapped in a single atomic operation. Neither is lost. This is the most powerful of the five commands because it allows you to work with the saved content while keeping a reference to the current line.

# Print the previous line each time a match is found
sed -n '/ERROR/ { x; p; x }' logfile.txt
# x brings the previous line into the pattern space
# p prints it
# x swaps back so the cycle continues normally

Step-by-Step Traces

The hold space is much easier to understand by watching it change cycle by cycle. Here are two detailed traces.

Trace 1: Printing the previous line on a match

Problem: print the line immediately before every line containing "ERROR".

Script: sed -n '/ERROR/{x;p;x}; x'

Input: "line A" / "line B" / "ERROR here" / "line D" Cycle 1 — read "line A" /ERROR/ → no match, block skipped x → PS="" (was hold), HS="line A" -n → no default print Cycle 2 — read "line B" /ERROR/ → no match, block skipped x → PS="line A", HS="line B" -n → no default print Cycle 3 — read "ERROR here" /ERROR/ → matches! enter block x → PS="line B", HS="ERROR here" p prints "line B" x → PS="ERROR here", HS="line B" x → PS="line B", HS="ERROR here" -n → no default print Cycle 4 — read "line D" /ERROR/ → no match x → PS="ERROR here", HS="line D" Final output: "line B"

Trace 2: Reversing two lines

Problem: swap every pair of lines (line 2 before line 1, line 4 before line 3, etc.).

Script: sed -n 'h;n;p;g;p'

Input: "first" / "second" / "third" / "fourth" Cycle 1 — read "first" h → HS="first" n → read next line; PS="second" (advances to next line) p prints "second" g → PS="first" (from hold) p prints "first" Cycle 2 — read "third" (n consumed "second", so next unread line is "third") h → HS="third" n → PS="fourth" p prints "fourth" g → PS="third" p prints "third" Final output: second / first / fourth / third

Classic Hold Space Recipes

Reverse the lines of a file (tac equivalent)

This is the most famous hold space pattern. It accumulates every line into the hold space in reverse order, then prints it all at the end:

# Reverse all lines — equivalent to: tac file.txt
sed -n '1!G; h; $p' file.txt

# Broken down:
#   1!G  — on every line except line 1: append hold→pattern
#           (so pattern = current line + newline + everything before it)
#   h    — copy that combined content back to hold space
#   $p   — on the last line: print the accumulated hold content
Input: "a" / "b" / "c" Cycle 1 — PS="a" 1!G skipped (IS line 1) h → HS="a" Cycle 2 — PS="b" G → PS="b\na" h → HS="b\na" Cycle 3 — PS="c" G → PS="c\nb\na" h → HS="c\nb\na" $p → prints c / b / a

Print the line before and after a match (context lines)

# Print the line BEFORE a match (using x to keep the previous line in hold)
sed -n '/ERROR/ { x; p }; x' logfile.txt

# Print the line AFTER a match (use n to advance and then print)
sed -n '/ERROR/ { n; p }' logfile.txt

# Print the matching line AND the line after it
sed -n '/ERROR/ { p; n; p }' logfile.txt

Accumulate lines into a block, then process the block

# Collect all lines of a paragraph (separated by blank lines) and print as one
sed -n '/^$/ { g; s/\n/ /g; p; d }; H' paragraphs.txt
# H accumulates lines into hold space
# On blank line: g retrieves, joins newlines with spaces, prints, resets

Remove duplicate consecutive lines (uniq equivalent)

# Delete a line if it is identical to the previous one
sed '$!N; /^\(.*\)\n\1$/!P; D' file.txt

# Broken down:
#   $!N              — on all but last line: append next line to pattern space
#                      pattern space now = "line1\nline2"
#   /^\(.*\)\n\1$/   — does pattern space match "X\nX" (two identical lines)?
#   !P               — if NOT identical: print first line only (P = multiline print)
#   D                — delete first line, restart cycle with second line in pattern space

Swap every pair of lines

# Print line 2 before line 1, line 4 before line 3, etc.
sed -n 'h; n; p; g; p' file.txt
# h saves current line; n reads the next line and prints it first;
# g retrieves saved line; p prints it second

Move a line to a different position

# Take line 1 (a header), delete it there, and append it after line 5
sed -n '1{ h; d }; 5{ p; g; p; d }; p' file.txt
# Line 1: save to hold, delete (skip print)
# Line 5: print it, then retrieve and print line 1 after it
# All other lines: print normally

Double-space a file (add blank line after every line)

# Using G with an empty hold space to insert a blank line after each line
sed 'G' file.txt
# The hold space starts empty, so G appends "\n" + "" = just a newline
# Result: every line is followed by a blank line

Delete a line and the one immediately following it

# Delete any line matching "REMOVE" and the line that follows it
sed '/REMOVE/ { N; d }' file.txt
# N appends the next line into the pattern space
# d deletes both lines together

Print only unique lines (remove ALL duplicates, not just consecutive)

# Collect all lines in hold space, then sort and uniq — but that needs a loop
# For a pure-SED approach (order-preserving, marks duplicates):
sed -n 'G; /^\(.*\)\n.*\1/!{ s/\n.*//; p; h }; s/.*\n//; H' file.txt
# This is complex — in practice, use: awk '!seen[$0]++'

The n and N Commands — Reading Ahead

The hold space commands are often used alongside two related commands that control how SED reads input. They are not hold space commands themselves, but they are closely related in practice and are essential for many multi-line patterns.

Command What it does Effect
n Next — read the next line Prints the current pattern space (unless -n), then reads the next input line into the pattern space, replacing the current one. The cycle continues from this point.
N Next append — append the next line Appends a newline + the next input line to the pattern space (does not print). The pattern space now contains two lines. Covered more fully in Chapter 7.
# n — skip the line after every blank line
sed '/^$/ { n; d }' file.txt
# When a blank line is found: n reads the next line into the pattern space,
# then d deletes it — effectively removing the line after every blank

# n — print every other line (even lines)
sed -n 'n; p' file.txt
# Cycle reads line 1 (odd), n reads line 2 (even) into pattern space, p prints it

# n — process pairs: substitute only on the second of each pair
sed '/^LABEL/ { n; s/value=.*/value=NEW/ }' config.txt

Avoiding Common Hold Space Mistakes

The leading newline from H

# PROBLEM: H on line 1 adds a blank line at the start
printf 'a\nb\nc\n' | sed -n 'H; ${ g; p }'
# Output has a leading blank line because hold starts empty

# FIX: use h on line 1 to initialise without a leading newline
printf 'a\nb\nc\n' | sed -n '1h; 1!H; ${ g; p }'
a
b
c

Forgetting that g wipes the pattern space

# PROBLEM: after g, the current line is gone
sed '/marker/ { h; g; s/marker/FOUND/ }' file.txt
# h saves the line, then g replaces the pattern space with the hold space
# — but hold space IS the same content, so this is a no-op here
# More dangerous: using g when the hold space has different content

# Use x to swap (preserving both) rather than g when you need both values

x on the first line exchanges with empty hold space

# The hold space starts empty — the first x puts that empty string in pattern space
printf 'a\nb\n' | sed -n 'x; /./p'
a
# Cycle 1: x swaps "a" to hold, "" to pattern — /./p doesn't print (pattern is empty)
# Cycle 2: x swaps "b" to hold, "a" to pattern — /./p prints "a"
# "b" is in hold space at end — never printed
# This is the "print previous line" pattern — intentional here, but easy to be caught out by

Practical Recipes

Extract a named section from an INI/TOML file

# Print only the [database] section (from header to next header or EOF)
sed -n '/^\[database\]/,/^\[/{/^\[database\]/!{ /^\[/d }; p}' config.ini

# Simpler approach using hold to stop at next section:
sed -n '/^\[database\]/{ :loop; p; n; /^\[/q; b loop }' config.ini

Join continuation lines (lines ending with \)

# Collapse lines ending in \ onto the next line (like make/shell line continuation)
sed -n ':join; /\\$/ { N; s/\\\n//; b join }; p' Makefile

Swap the first and last lines of a file

sed -n '1{ h; d }; 2,${p}; ${ g; p }' file.txt
# Line 1: save to hold, delete
# Lines 2 to second-to-last: print normally
# Last line: print it (already happened via 2,$p), then retrieve and print line 1

Print lines between two patterns, excluding the markers

sed -n '/^START$/ { n; /^END$/q; :l; p; n; /^END$/q; b l }' file.txt

Annotate each line with the previous line as a comment

sed 'G; s/\n/\n# Previous: /' file.txt
# G appends the hold space (previous line) after the current line
# s replaces the joining newline with a newline + "# Previous: " prefix

Quick Reference — Chapter 6

Pattern Space → Hold Space

h Copy pattern space to hold space (overwrites hold)
H Append newline + pattern space to hold space

Hold Space → Pattern Space

g Copy hold space to pattern space (overwrites pattern space)
G Append newline + hold space to pattern space

Exchange

x Swap pattern space and hold space simultaneously

Related — Reading Ahead

n Print current pattern space, then read next line into it
N Append next line to pattern space (no print) — see Chapter 7

Key Patterns

1!G; h; $p Reverse all lines (tac equivalent)
G Double-space a file (hold starts empty → blank line after each)
1h; 1!H; ${g;p} Accumulate all lines, print as single block at end
/pat/{x;p};x Print the line before every match
What is coming next: Chapter 7 covers multi-line commands in depth — N, P, and D. These three commands let SED work across line boundaries: joining lines together, printing only part of a multi-line pattern space, and looping back to the start of the script with modified content. They are the foundation of SED's ability to process records that span multiple lines.