Your country

Tools that support it use your country for local currency, number formats, units and paper size. Your choice is saved only in this browser.

Type a name or a two-letter code. Use the up and down arrow keys to move through the countries, Enter to choose one and Escape to close.

Bash & the POSIX Shell  Module 11 – Text processing: the Unix toolbox

sed beyond s///: hold space and multiline

Process lines together with the hold space (h, H, g, G, x), read across boundaries (N, P, D), branch with labels, and insert text with a, i and c.

  • Advanced
  • 25 minutes
  • Examples run with Bash 5.3.20, dash 0.5.13.5, shellcheck 0.11.0 and Python 3.14.8
  • By MySmartCoPilot
Free lesson, Pro answers: 25 minutes
Free: the whole lesson. With a pass: the model answers to its 2 interview questions.

What you will learn

  • Move text between the pattern space and hold space with h, H, g, G and x
  • Read multiple lines into the pattern space with N, and print or delete the first line with P or D
  • Loop with labels, b and t to build conditional flow and state machines
  • Add, replace and change lines with a, i and c

Before you start

What you’ll learn · Before you start

What you will learn

  • Move text between the pattern space and hold space with h, H, g, G and x
  • Read multiple lines into the pattern space with N, and print or delete the first line with P or D
  • Loop with labels, b and t to build conditional flow and state machines
  • Add, replace and change lines with a, i and c

Before you start

On this page

A configuration file wraps long lines with a trailing backslash, and every tool that reads it unwraps them first. sed can join those lines before anything else sees them, but s/// sees one line at a time and has nowhere to put a line it wants to remember. The hold space: A second buffer that sed keeps across cycles; the h, H, g, G and x commands copy or exchange data between it and the pattern space. It starts empty. is that somewhere: a second buffer that sed never touches unless you ask, where a script can stash a line and bring it back later.

You will move text between the pattern space and the hold space with h, H, g, G and x, read several input lines into one pattern space with N, carve the first line out again with P and D, jump around a script with labels and branches, and insert, append or replace whole lines. The examples use BSD sed (macOS) for portability; many scripts work the same on GNU sed 4.10, but labels and the one-line insert form need script files on BSD sed.

The hold space: h, H, g, G and x

sed reads each input line into the pattern space: sed's working buffer. At the start of every cycle sed reads a line from the input into it (without the trailing newline), applies every command whose address matches, and prints it at the end of the cycle unless -n is given. More in “sed: substitute, delete and print”, runs the script, prints what remains (unless -n was passed), clears the pattern space and starts the next line. The hold space is empty at the start and keeps its value across lines. Five commands copy or swap text between the two:

Command Effect
h Copy the pattern space to the hold space, replacing it
H Append the pattern space to the hold space, with a newline before it
g Copy the hold space to the pattern space, replacing it
G Append the hold space to the pattern space, with a newline before it
x Swap the two spaces
h, H, g, G and x: moving text between the two buffers Python · hold_session.py
"""Hold space commands: h, H, g, G, x"""

from session import run, write

write("lines.txt", "one\ntwo\nthree\n")

run("""
# Input file
cat lines.txt

# Reverse lines: 1!G;h;$!d
sed '1!G;h;$!d' lines.txt

# Double-space: G appends hold (empty) + newline
sed 'G' lines.txt | head -6

# Print last line only with hold space
sed -n '$p' lines.txt
""")

Output

# Input file
$ cat lines.txt
one
two
three
# Reverse lines: 1!G;h;$!d
$ sed '1!G;h;$!d' lines.txt
three
two
one
# Double-space: G appends hold (empty) + newline
$ sed 'G' lines.txt | head -6
one

two

three

# Print last line only with hold space
$ sed -n '$p' lines.txt
three

Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 hold_session.py

The session showed the classic reverse-file idiom: sed '1!G;h;$!d' printed three, two, one from an input of one, two, three. On line 1, 1!G does not run, h copies one to the hold space, and $!d deletes the pattern space so nothing prints. On line 2, G appends the hold space (one) after a newline, so the pattern space becomes two\none; h copies that to the hold space; $!d deletes again. On line 3, the last, G makes the pattern space three\ntwo\none; $!d does not run, so the whole thing prints.

Interactive · step by step The reverse-file idiom: 1!G;h;$!d Watch the hold space collect the lines in reverse; the pattern space prints only on the last line.
The reverse-file idiom: 1!G;h;$!d, step 10 of 10. Showing Input (three), Pattern space ((printed)), Hold space (two\none), Output (three\ntwo\none). In focus: Output.readh/GprintInputthreePattern space(printed)Hold spacetwo\noneOutputthree\ntwo\none

The drawing shows the whole scene; its 10 steps are listed below.

Every step as text
  1. Line 1 read: 'one'. sed reads 'one' into the pattern space; the hold space is still empty.
  2. Line 1: h copies. 1!G is skipped on line 1; h copies the pattern space to the hold space.
  3. Line 1: $!d deletes. $!d deletes the pattern space on every line except the last; nothing prints.
  4. Line 2 read: 'two'. sed reads 'two' into the pattern space; the hold space still has 'one'.
  5. Line 2: G appends hold. G appends the hold space with a newline before it; pattern space becomes two\none.
  6. Line 2: h copies. h copies the joined text back to the hold space.
  7. Line 2: $!d deletes. $!d deletes again; nothing prints yet.
  8. Line 3 read: 'three'. sed reads 'three'; the hold space already has the earlier two lines reversed.
  9. Line 3: G appends hold. G appends two\none to the pattern space, which becomes three\ntwo\none.
  10. Line 3: $!d is skipped, print. $! says 'not the last line', so d does not run; sed prints the whole pattern space.

The same building blocks assemble in other ways. sed -n '$p' prints only the last line. sed '$!d' does the same by deleting every line that is not the last. sed -n '1!G;h;$p' reverses lines and prints only at the end. sed 'G' double-spaces a file by appending the (empty) hold space plus a newline to every line.

Key idea

The hold space is persistent scratch paper: it survives from one input line to the next. Use it when a script must remember something and act on it later, not when every line stands alone.

Checkpoint

Check: Hold space commands

  1. Question 1 of 2 Which sed command copies the pattern space to the hold space, replacing its contents?

    Choose one answer.

    Show the answer: Question 1 of 2

    Answer: h

    Lower-case h and g replace; upper-case H and G append with a newline. The mnemonic is that lower case is a single copy and upper case accumulates.

  2. Question 2 of 2 The idiom sed '1!G;h;$!d' reverses a file. On line 2 of a three-line file, what is in the hold space after h runs?

    Choose one answer.

    Show the answer: Question 2 of 2

    Answer: line2\nline1

    On each line except the first, G appends the accumulated earlier lines (oldest at the bottom) to the current line, then h saves the new stack. The final print shows the stack in reverse order.

Reading across lines: N, P and D

Some patterns span more than one line. A trailing backslash means “the next line continues this one”; a blank line separates paragraphs; a multi-line block of configuration runs from <VirtualHost> to </VirtualHost>. The N command reads the next input line and appends it to the pattern space with an embedded newline. P prints up to the first newline. D deletes up to the first newline and restarts the cycle without reading new input if anything remains.

N, P and D: joining and splitting lines Python · multiline_session.py
"""Multiline commands: N, P, D"""

from session import run, write

write("pairs.txt", "a\nb\nc\nd\n")

run("""
# Input file for joining
cat pairs.txt

# Join every two lines with +
sed 'N;s/\\n/+/' pairs.txt

# Protect last line on odd-count files
printf 'a\\nb\\nc\\n' | sed '$!N;s/\\n/+/'

# Sliding window: print with N;P;D (each pair)
printf 'a\\nb\\nc\\n' | sed 'N;P;D'

# N appends, showing embedded newline
printf 'first\\nsecond\\n' | sed 'N; l'
""")

Output

# Input file for joining
$ cat pairs.txt
a
b
c
d
# Join every two lines with +
$ sed 'N;s/\n/+/' pairs.txt
a+b
c+d
# Protect last line on odd-count files
$ printf 'a\nb\nc\n' | sed '$!N;s/\n/+/'
a+b
c
# Sliding window: print with N;P;D (each pair)
$ printf 'a\nb\nc\n' | sed 'N;P;D'
a
b
# N appends, showing embedded newline
$ printf 'first\nsecond\n' | sed 'N; l'
first$
second$
first
second

Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 multiline_session.py

The session showed sed 'N;s/\n/+/' joining pairs of lines with a +: a and b became a+b, and c and d became c+d. N read the second line and left the pattern space as a\nb; s/\n/+/ turned the embedded newline into a plus. When there is no next line (an odd-numbered file), N quits the script without printing, so sed '$!N;s/\n/+/' protects the last line: the three-line input a b c became a+b and c.

Interactive · step by step Joining pairs: N;s/\n/+/ N reads the next line into the pattern space; s turns the embedded newline into a plus.
Joining pairs: N;s/\n/+/, step 8 of 8. Showing Input ((exhausted)), Pattern space ((printed)), Output (a+b\nc+d). In focus: Output.read/NprintInput(exhausted)Pattern space(printed)Outputa+b\nc+d

The drawing shows the whole scene; its 8 steps are listed below.

Every step as text
  1. Line 1 read: 'a'. sed reads 'a' into the pattern space; input still has three lines queued.
  2. N appends 'b'. N reads the next line and appends it with an embedded newline; pattern becomes a\nb.
  3. s replaces the newline. s/\\n/+/ turns the embedded newline into a plus; the two lines are joined.
  4. Default print. End of cycle; sed prints the pattern space and clears it.
  5. Line 3 read: 'c'. sed reads 'c' into the pattern space; one line remains in the input.
  6. N appends 'd'. N reads 'd' and appends it; pattern becomes c\nd.
  7. s replaces the newline. s/\\n/+/ turns the newline into a plus; the second pair is joined.
  8. Default print. End of cycle; sed prints the pattern space. Two pairs have been joined.

P and D are the inverse. sed 'N;P;D' is a two-line sliding window: every pair of adjacent lines passes through the pattern space. The session showed three lines going through the window: a and b printed a, then b and c printed b, and the final c was lost because N quit when there was no fourth line. D keeps the second line for the next cycle, where N appends the third, and so on. A continuation-line joiner works the same way: when a line ends with a backslash, N reads the next, s/\\\n// removes the backslash and the embedded newline, and a branch jumps back to try again.

Sliding window versus paragraph mode

N;P;D keeps two lines at a time. For paragraph mode, where records are separated by blank lines, use sed -z on GNU sed 4.2.2 and later, or switch to awk with RS="".

Checkpoint

Check: Multiline commands

  1. Question 1 of 2 What does the N command do when it runs?

    Choose one answer.

    Show the answer: Question 1 of 2

    Answer: Appends the next input line to the pattern space, separated by a newline

    N is "next, appended". After N, the pattern space may contain embedded newlines, and s/\n/…/ can operate on them.

  2. Question 2 of 2 The idiom sed 'N;P;D' is called a sliding window. What does P do in that sequence?

    Choose one answer.

    Show the answer: Question 2 of 2

    Answer: Prints the pattern space up to the first newline

    P and D are the multi-line counterparts of p and d. P prints up to the first newline, D deletes up to it and restarts the cycle with what remains.

Branches and labels: b, t and the loop

sed scripts run from top to bottom on each line, but two commands break the flow. b label jumps to :label unconditionally (or to the end if no label is given). t label jumps only if a substitution has succeeded since the last input line or the last t. Together they build loops and state machines.

Labels and branches: loops and conditional jumps Python · branch_session.py
"""Branch commands: labels, b and t"""

from session import run, write

write("dashes.txt", "a--b---c\nx--y\n")
write("data.txt", "# comment\nfoo bar\nbaz foo\n")
write("loop.sed", ":loop\ns/--/-/\nt loop\n")
write("skip.sed", "/^#/b\ns/foo/FOO/\n")

run("""
# File with multiple dashes
cat dashes.txt

# Replace all -- with - using a t loop (script file)
cat loop.sed
sed -f loop.sed dashes.txt

# File with comments
cat data.txt

# Skip substitution on comment lines with b (script file)
cat skip.sed
sed -f skip.sed data.txt

# Conditional processing: only non-blank lines
printf 'a\\n\\nb\\n' | sed -e '/^$/b' -e 's/^/> /'
""")

Output

# File with multiple dashes
$ cat dashes.txt
a--b---c
x--y
# Replace all -- with - using a t loop (script file)
$ cat loop.sed
:loop
s/--/-/
t loop
$ sed -f loop.sed dashes.txt
a-b-c
x-y
# File with comments
$ cat data.txt
# comment
foo bar
baz foo
# Skip substitution on comment lines with b (script file)
$ cat skip.sed
/^#/b
s/foo/FOO/
$ sed -f skip.sed data.txt
# comment
FOO bar
baz FOO
# Conditional processing: only non-blank lines
$ printf 'a\n\nb\n' | sed -e '/^$/b' -e 's/^/> /'
> a

> b

Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 branch_session.py

The session’s label loop (stored in a script file loop.sed) replaced -- with a single - until none remained: a--b---c became a-b-c. The script :loop on one line, s/--/-/ on the next and t loop on the third set up a label, tried the substitution, and branched back to it only when the substitution succeeded. When nothing changed, the branch did not fire and sed moved on. t tests the flag set by the most recent s; two substitutions in a row clear and set the same flag.

b is the unconditional jump. The session showed a script file skip.sed with /^#/b and s/foo/FOO/ on separate lines: comment lines jumped to the end and printed unchanged, while other lines had foo replaced with FOO. b without a label means “jump to the end of the script, print and read the next line”. That is the simplest guard: the line still prints, but the rest of the script does not run on it.

Key idea

Use b to skip commands and t to repeat commands. A :label … t label loop runs until a substitution stops matching; a :label … b label loop runs forever unless the script calls d or q.

Checkpoint

Check: Branches and labels

  1. Question 1 of 2 When does t label branch to :label?

    Choose one answer.

    Show the answer: Question 1 of 2

    Answer: When a substitution has succeeded since the last input line or the last t

    t is the conditional branch after a successful substitution. A loop such as :loop; s/--/-/; t loop keeps running until the s command stops matching.

  2. Question 2 of 2 What happens when sed runs b without a label?

    Choose one answer.

    Show the answer: Question 2 of 2

    Answer: Jumps to the end of the script, prints the pattern space and reads the next line

    sed '/^#/b; s/foo/bar/' skips the substitution on comment lines by branching to the end, where the line prints unchanged.

Inserting, appending and changing lines: a, i and c

Sometimes a script must add text rather than transform what is already there. a text appends text after the current line is printed. i text inserts text before the current line. c text replaces the current line with text. In POSIX, the text follows a backslash and a newline (so a POSIX insert on a blank line reads: /^$/i\ on one line, --- on the next).

GNU sed accepts a one-line form, 2i inserted, but the browser runner and older versions need the backslash form. All three commands queue their text and print it at the right moment; the pattern space is unchanged by a and i, but c clears it and starts the next cycle.

a, i and c: adding and replacing lines Python · insert_session.py
"""Insert, append and change: a, i, c"""

from session import run, write

write("data.txt", "alpha\nbeta\ngamma\n")
# POSIX form requires backslash-newline for a, i, c
write("insert.sed", "1i\\\nheader\n")
write("append.sed", "$a\\\nfooter\n")
write("change.sed", "2c\\\nreplaced\n")

run("""
# Input file
cat data.txt

# Insert header before first line (POSIX form)
cat insert.sed
sed -f insert.sed data.txt

# Append footer after last line
cat append.sed
sed -f append.sed data.txt

# Change (replace) line 2
cat change.sed
sed -f change.sed data.txt
""")

Output

# Input file
$ cat data.txt
alpha
beta
gamma
# Insert header before first line (POSIX form)
$ cat insert.sed
1i\
header
$ sed -f insert.sed data.txt
header
alpha
beta
gamma
# Append footer after last line
$ cat append.sed
$a\
footer
$ sed -f append.sed data.txt
alpha
beta
gamma
footer
# Change (replace) line 2
$ cat change.sed
2c\
replaced
$ sed -f change.sed data.txt
alpha
replaced
gamma

Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 insert_session.py

The session showed sed scripts in files using the POSIX backslash-and-newline form: insert.sed with 1i\ and header on the next line inserted header before the first line; append.sed with $a\ and footer appended footer after the last; and change.sed with 2c\ and replaced replaced the second line entirely. GNU sed accepts a one-line form (2i inserted), but BSD sed on macOS requires the backslash form or a script file.

The one-line form is a GNU extension

Wrong: relying on sed '1i header' file in a script that runs on BSD sed (macOS without Homebrew) or in the browser runner. Right: use the backslash-and-newline form 1i\ with the text on the next line, or write the script in a file.

Checkpoint

Check: Insert, append and change

  1. One question Which sed command inserts text before the current line?

    Choose one answer.

    Show the answer: One question

    Answer: i

    The mnemonic: i for insert (before), a for append (after), c for change (replace).

Practise: join, split and transform

Work through these tasks in your terminal, using the commands from this lesson. Check your answers below.

  1. Reverse the lines of a file without tac. Expected command: sed '1!G;h;$!d' file.
  2. Join every two lines with a colon. Expected: sed 'N;s/\n/:/'.
  3. Double-space a file (add a blank line after every line). Expected: sed G file.
  4. Remove all consecutive duplicate lines (like uniq). Hint: N;/^\(.*\)\n\1$/!P;D.
  5. Join lines that end with a backslash to the next line. Hint: :a; /\\$/N; s/\\\n//; ta.

Practice that adapts to you

Practise this lesson

7 questions about 4 ideas of this lesson. Each answer is explained at once, and what you get right is kept in this browser.

  1. Question 1 of 7 Which buffer does sed load each input line into?

    Choose one answer.

    Show the answer: Question 1 of 7

    Answer: The pattern space

    The pattern space is the working area; the hold space is scratch paper the script controls.

  2. Question 2 of 7 What does G do when the hold space is empty?

    Choose one answer.

    Show the answer: Question 2 of 7

    Answer: Appends a newline to the pattern space

    That is why sed G double-spaces a file: G appends a newline and the (empty) hold space, so every line gains a trailing newline.

  3. Question 3 of 7 What happens when N runs on the last input line?

    Choose one answer.

    Show the answer: Question 3 of 7

    Answer: sed quits the script without printing the line

    Protect the last line with $!N (do not run N on the last line) or handle the odd-line case another way.

  4. Question 4 of 7 After D deletes up to the first newline, what does sed do with the text that is left?

    Choose one answer.

    Show the answer: Question 4 of 7

    Answer: The cycle restarts at the beginning of the script with the remaining pattern space

    That restart is what makes N;P;D a sliding window: D keeps the second line and goes back to N, which appends the third.

  5. Question 5 of 7 What resets the flag that t tests?

    Choose one answer.

    Show the answer: Question 5 of 7

    Answer: Reading a new input line or running t itself

    The GNU sed manual describes the substitution flag under "Conditional branch".

  6. Question 6 of 7 What happens to the pattern space after c text runs?

    Choose one answer.

    Show the answer: Question 6 of 7

    Answer: It is cleared and the next cycle starts

    c is a complete replacement, so the rest of the script does not run on that line.

  7. Question 7 of 7 Which insert form is POSIX-compliant?

    Choose one answer.

    Show the answer: Question 7 of 7

    Answer: A backslash, a newline, and the text on the next line

    Scripts that must run on BSD sed (macOS without Homebrew) or in the browser runner need the POSIX form.

Interview questions

Warm-up (fresher to 2–5 years): what are the two buffers sed uses, and when does each clear?

sed has a pattern space and a hold space. The pattern space is where sed loads each input line, runs the script, and (unless -n) prints what remains; it clears automatically before each new input line. The hold space starts empty and keeps its value until the script changes it, which is what makes it useful for remembering earlier lines. h and H copy or append to the hold space; g and G copy or append from it; x swaps the two.

Think through the following scenarios before you open the answer page:

Model answers open with any pass: ₹79 for 1 week ($2.99), one payment that never renews.

  1. Mid to senior

    A shell script joins continuation lines (those ending with a backslash) to the next line with a sed script that uses N and a branch loop. The output doubles every joined line: "line1 continued" appears twice. What could cause that, and how do you fix it?

    Model answer, Pro. Model answers open with any pass; prices are in the notice on this page. First step and follow-ups

  2. Fresher to mid

    A sed one-liner meant to reverse the lines of a file dropped the last line from the output. The input had three lines, the output had two. The script was sed '1!G;h;d'. What happened, and what small change fixes it?

    Model answer, Pro. First step and follow-ups

Flashcards · 5 cards

Recall it, then check

Say the answer to yourself before you look: recalling it is what makes it stick.

  1. What are sed's two buffers, and when does each clear?

    The pattern space holds the current line and is cleared before each new line is read. The hold space starts empty and keeps its value until the script changes it, which lets a script remember earlier lines.

  2. What do h, H, g, G and x do?

    h copies the pattern space to the hold space and H appends to it; g copies the hold space back and G appends it; x swaps the two.

  3. How do N, P and D let sed work across lines?

    N appends the next line to the pattern space after a newline, P prints up to the first newline, and D deletes up to it and restarts the cycle.

  4. What do b and t do in a sed script?

    b label jumps every time, and t label jumps only when a substitution succeeded since the last input line, so :loop, s/--/-/ and t loop on three lines repeat until no match is left. BSD sed needs each label on a line of its own.

  5. How do you append, insert or change lines portably with sed?

    a, i and c add a line after, add one before, or replace the line; their one-line form is GNU-only, so portable scripts write the text after a backslash and a newline.

Key takeaways

  • The hold space is a second buffer that sed never touches unless the script says so; h, H, g, G and x move text between it and the pattern space.
  • N appends the next line to the pattern space with an embedded newline, P prints up to the first newline and D deletes up to it and restarts the cycle.
  • b label jumps unconditionally, t label jumps only when a substitution succeeded since the last line; together they build loops such as :loop; s/--/-/; t loop.
  • a text, i text and c text insert, append or replace lines; the one-line form is GNU-only, so portable scripts use the backslash-and-newline form.
  • When a sed script grows past a few commands, awk is often clearer.

Terms in this lesson

hold space
A second buffer that sed keeps across cycles; the h, H, g, G and x commands copy or exchange data between it and the pattern space. It starts empty.

References

Report a problem with this lesson

Quick answers and tool search

Type to search tools or to get a quick answer, for example 18% of 2500. Use the up and down arrow keys to move through the results, Enter to choose, and Escape to close.