Skip to content
VLSI Mentor

sed Part II · Module 6 · Advanced sed Processing

Branching and Conditional Execution

Every script so far has run top to bottom, once per line. This chapter adds the two commands that change that: b jumps to a label, and t jumps only if the last substitution actually changed something. Together they give you a loop — repeat a substitution until it stops finding anything — which is how you squeeze arbitrary runs of whitespace, or join an unknown number of continuation lines. It is also the chapter with the most dangerous portability trap in this track, and not because the trap is obscure: the compact one-line form that nearly every example online uses runs on BSD sed, prints a warning you will not see, leaves your file completely unchanged, and exits with status zero.

Advanced21 min readsedPart IIControl FlowPortabilityAdvanced

Part II · Module 6 · Chapter 6.3 · Branching and Conditional Execution

1. Three Pieces

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
   :name     a LABEL — marks a place in the script. Does nothing by itself.
   b name    BRANCH — jump to that label, always.
   t name    TEST — jump to that label ONLY IF the last s changed something.
   b         a bare b — jump to the END of the script, skipping the rest.

t is the useful one. It is the only command in sed that asks "did that work?", and it is what lets a script repeat an operation an unknown number of times.

A label is just a marker. :squeeze creates no behaviour; it only gives b and t somewhere to jump to.

2. Writing It So It Works

Before any examples, the syntax, because getting this wrong is the subject of section 4.

On BSD sed — which is what macOS ships — a label name extends to the end of the line, and so does the target of a b or t. That means a label and the command after it cannot share a line. There are two correct forms:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
   sed -e ':a' -e 's/  / /' -e 'ta' file        one -e per line
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
   sed -f script.sed file                        a script file, one command per line

Both work everywhere, GNU included. Everything in this chapter uses one of the two.

3. t — Branch If the Substitution Worked

A two-arm demonstration. The script tries a substitution; if it worked, it jumps past a marker, and if it did not, the marker gets added:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
printf 'aaa\nbbb\n' | sed -e 's/a/X/' -e 'tdone' -e 's/^/NO-SUB: /' -e ':done'
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
Xaa
NO-SUB: bbb

Both arms, in one run:

  • aaa — s/a/X/ changed it to Xaa, so tdone jumped to the label and the marker command was skipped.
  • bbb — the substitution found no a, so tdone did not jump, execution continued, and the marker was prepended.

That is t in full. Two rules make it predictable, and both matter for loops:

  • The "did a substitution succeed" flag is reset when a new line is read, so each line starts fresh.
  • It is also reset when a t actually jumps. So a loop's t tests only whether the substitution inside this iteration worked — which is what makes the loop stop rather than spinning forever.

4. The Compact Form, and Why It Is Dangerous Here

Now the trap. The registered example for this chapter is written in the compact GNU style:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
sed ':a; s/[[:space:]][[:space:]]/ /; ta' sta/timing.rpt

On GNU sed that squeezes every run of spaces down to one. On the BSD sed these examples were verified against, here is what happens. First, standard error:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
sed: 2: ":a; s/[[:space:]][[:spa ...": unused label 'a; s/[[:space:]][[:space:]]/ /; ta'

Read the label name in that message: a; s/[[:space:]][[:space:]]/ /; ta. The whole rest of the line became the name of the label, because that is the BSD rule from section 2. So the script contained one label and no commands at all.

Second, standard output — nine lines, the file completely unchanged. Counting the lines that still contain two consecutive spaces:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
grep -c '  ' <output of the command above>
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
7

Seven lines still have double spaces. And third, the exit status:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
0

A warning on stderr, the input passed through untouched, and a success status. Compare that with the portable form:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
sed -e ':a' -e 's/[[:space:]][[:space:]]/ /' -e 'ta' sta/timing.rpt
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
Startpoint: u_alu/rd_data_reg[3]
Endpoint: u_fifo/wr_ptr_reg[0]
 data arrival time 1.842
 data required time 2.100
 slack (MET) 0.258
Startpoint: u_fifo/rd_ptr_reg[1]
Endpoint: u_alu/op_reg[2]
 data arrival time 2.410
 slack (VIOLATED) -0.110

Zero lines with double spaces. The two commands are supposed to do the same thing and differ by nothing but line breaks.

The bare-b one-liner fails differently, and more usefully:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
sed '/^Startpoint/,/^[[:space:]]*slack/!b; s/^/  /' sta/timing.rpt
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
sed: 2: "/^Startpoint/,/^[[:spac ...": undefined label '; s/^/  /'

Exit status 1 — nothing is produced, which you cannot miss. Same syntax rule, opposite visibility: here the swallowed text became a branch target that does not exist, so sed refused to run; in the :a case it became a label definition, which is harmless and therefore silent.

Five steps forming a loop. A label marks the top of the loop. The substitution runs once. The t command tests whether that substitution changed anything. If it did, control jumps back to the label and the substitution runs again. If it did not, the loop ends and the rest of the script runs.A loop built from a label and t01A LABEL MARKS THE TOP OF THE LOOPthe colon form does nothing on its own02THE SUBSTITUTION RUNS ONCEone pair of spaces becomes one space03T ASKS WHETHER THAT SUBSTITUTION CHANGED ANYTHINGthe only conditional sed has04IF IT CHANGED SOMETHING, JUMP BACK AND REPEATthe flag resets, so each pass tests only itself05IF IT CHANGED NOTHING, CARRY ONthe line is finished; the loop cannot spin
Figure 1 — the loop that t builds. The only branch is at step 4: if the substitution changed something there is probably more to do, so jump back; if it did not, the line is finished and the script continues. Because the flag resets whenever the branch is taken, each pass tests only its own substitution, which is what makes the loop terminate.

5. The Loop That Squeezes Whitespace

A single substitution with g replaces every non-overlapping match once. That is not the same as repeating until nothing changes — four consecutive spaces contain two non-overlapping pairs, so one pass with g turns them into two spaces, not one. Repeating is what finishes the job, and t is how you repeat.

As a script file, which is where this belongs:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
cat -n scripts/squeeze.sed
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
     1	:a
     2	s/[[:space:]][[:space:]]/ /
     3	ta
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
sed -f scripts/squeeze.sed sta/timing.rpt
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
Startpoint: u_alu/rd_data_reg[3]
Endpoint: u_fifo/wr_ptr_reg[0]
 data arrival time 1.842
 data required time 2.100
 slack (MET) 0.258
Startpoint: u_fifo/rd_ptr_reg[1]
Endpoint: u_alu/op_reg[2]
 data arrival time 2.410
 slack (VIOLATED) -0.110

Every column of padding collapsed to one space. On the data arrival time line that is around thirty spaces reduced by about twenty-nine successive passes, all driven by three lines of script.

This is the standard normalisation step before diffing two tool reports: with the padding gone, a diff shows you the values that changed rather than the columns that moved. Chapter 6.4 builds exactly this into a reusable script file.

6. A Bare b — Skip the Rest

b with no label jumps to the end of the script. Combined with a negated address it reads as "unless this line is interesting, stop here" — a guard clause:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
cat -n sta/report.rpt
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
     1	Report : timing
     2	Design : top
     3	Startpoint: u_alu/rd_data_reg[3]
     4	Endpoint:   u_fifo/wr_ptr_reg[0]
     5	  slack (MET)                              0.258
     6	1 path reported
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
sed -e '/^Startpoint/,/^[[:space:]]*slack/!b' -e 's/^/  /' sta/report.rpt
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
Report : timing
Design : top
  Startpoint: u_alu/rd_data_reg[3]
  Endpoint:   u_fifo/wr_ptr_reg[0]
    slack (MET)                              0.258
1 path reported

Lines 3–5 gained two spaces; the header and footer did not. Without the guard, everything is indented:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
sed -e 's/^/  /' sta/report.rpt
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
  Report : timing
  Design : top
  Startpoint: u_alu/rd_data_reg[3]
  Endpoint:   u_fifo/wr_ptr_reg[0]
    slack (MET)                              0.258
  1 path reported

7. Think It Through

1. What is the difference between b and t?

b always jumps. t jumps only if a substitution has changed the pattern space since the last line was read or the last successful t. b is for skipping; t is for looping and for asking "did that work".

2. Write a portable command that collapses runs of two or more spaces into one.

sed -e ':a' -e 's/ / /' -e 'ta' file, or the same three lines in a -f script. The label and the commands must be on separate -e fragments or separate lines — section 4 is what happens otherwise. Note s/ / /g alone is not equivalent: with g it handles non-overlapping pairs in one pass, so four spaces become two rather than one.

3. A colleague's normalisation step "runs fine" and the two reports still differ on every line. Their script is sed ':a; s/ / /; ta'. What do you tell them?

That on BSD sed the entire script became a label name, so nothing was substituted — and the only evidence was a stderr warning, because stdout was the unchanged file and the exit status was 0. Rewrite it with one -e per command or move it to a -f file. Then check with grep -c ' ' rather than by eye.

4. Why does a t loop terminate?

Because the flag is reset each time the branch is taken, so each pass tests only its own substitution — and because the substitution shrinks the line. When no pair of spaces is left, that pass changes nothing, t does not jump, and the script falls through. A loop whose substitution does not shrink the line is not guaranteed to stop.

5. When is a bare b better than putting an address on the command?

When more than one command should be skipped. For one command, /addr/s/…/…/ is clearer. For four, /addr/!b at the top is a guard clause that applies to all of them without wrapping them in a block — though a { } block from Chapter 5.4 is an equally good answer and often easier to read.

6. You need to join every continuation line onto its parent, however many there are. Sketch it.

A loop around the N from Chapter 6.2: label, then $!N, then a substitution that joins a newline-plus-indent, then t back to the label — so the window keeps growing while continuations keep being found. In a script file:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
:join
$!N
s/\n[[:space:]]\{4,\}/ /
tjoin
P
D

That is the general form of Chapter 6.2's fixed two-line version. It is also about as complex as a sed script should get before awk becomes the better tool.

8. Summary

  • A label marks a place; b jumps there always; t jumps only if the last substitution changed something. A bare b jumps to the end of the script.
  • t is the only conditional in sed, and the flag resets when a new line is read and when a branch is taken — which is why loops terminate.
  • On BSD sed a label name runs to the end of the line. A label and a command cannot share one.
  • The compact ':a; s/…/; ta' form is the worst trap in this track: a stderr warning, the input unchanged on stdout, and exit status 0. Seven lines still had double spaces where the portable form left none.
  • The bare-b one-liner fails loudly instead — undefined label, exit 1 — from the same syntax rule. The difference in visibility is luck, not design.
  • Write one -e per command, or use a -f script file. This is a structural fix, not a matter of care.
  • A t loop is how you repeat until done. s/ / /g in one pass is not the same thing: four spaces become two, not one.
  • A loop's substitution must shrink the line or the loop may never end.
  • A guard clause is /addr/!b — and testing it needs a fixture with lines outside the range, or the guarded and unguarded versions look identical.

Three chapters of this module have now ended with the same recommendation: put the script in a file. A hold-space pairing, a sliding window and a t loop are all past the point where a one-liner is reviewable, and branching in particular is only reliably writable that way. Chapter 6.4 — sed Script Files and Reusable Transformations closes Module 6 by treating the script as what it has become — flow code that belongs in version control, with comments, next to the thing it transforms.

Where this fits

Part of the sed curriculum.