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.
Cleared at end of every cycle.
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 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'
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'
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
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
Hold Space → Pattern Space
Exchange
Related — Reading Ahead
Key Patterns
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.