sed Part II · Module 5 · Filtering and Line Operations
Combining Commands and Scoped Operations
This chapter closes Module 5 and with it Part II, and it is where everything so far becomes one script. You have the cycle, the pattern language, the addresses, and the full set of line-level commands. What is left is composition: writing several commands together, and — far more important — giving them a region to work in. That second part is what separates a sed script you can defend in review from one that happened to work. A substitution that is exactly right inside a timing path block is usually wrong applied to a whole report, and an address on a { } block is how you say so.
Intermediate19 min readsedPart IICompositionSTA ReportsFilelists
Part II · Module 5 · Chapter 5.4 · Combining Commands and Scoped Operations
1. Scripts Have More Than One Command
Everything so far has mostly been one command at a time. Real sed is a short list of them, applied in order to each line — which Chapter 2.2 established is the whole execution model.
There are two things to get right, and they are not equally important:
- How to write several commands. Three spellings, all equivalent. Ten minutes of syntax.
- How to limit where each one applies. This is the part that decides whether the script is correct.
Section 2 is the first; the rest of the chapter is the second.
2. Three Ways to Write Two Commands
printf 'a\n' | sed -e 's/a/b/' -e 's/b/c/'cprintf 'a\n' | sed 's/a/b/; s/b/c/'csed 's/a/b/
s/b/c/'Run on the same input, that third form also prints c. All three are the same script — commands applied in order, each seeing what the last one left, exactly as Chapter 2.2 described.
Which to use:
- A semicolon for two or three short commands on one line. Compact and readable.
- Repeated
-ewhen each command deserves its own line in a shell script, so a diff shows which one changed. - A real newline inside one quoted script, which is the same idea without the repetition.
- A script file with
-fonce it outgrows all three — Chapter 2.3 introduced that, and Module 6 returns to it.
The one rule that bites: a, i and c swallow everything to the end of the line, so a semicolon after them becomes part of the text. Section 6 measures it.
3. One Address Changes the Blast Radius
Here is the heart of the chapter, in two commands that differ by one address.
A filelist mid-migration:
cat -n filelists/top.f 1 +define+DEBUG
2 # indented comment
3 +incdir+/proj/old/rtl
4
5 ./rtl/alu.sv
6 # a commented-out file
7
8 /proj/old/tb/env.sv
9 ./rtl/top.svTwo lines mention /proj/old/ — the +incdir+ search path on line 3, and a source file on line 8.
Suppose the include directory has moved but the testbench tree has not. The registered example scopes the substitution to the lines that own it:
sed '/+incdir+/ s|/proj/old/|/proj/new/|g' filelists/top.f+define+DEBUG
# indented comment
+incdir+/proj/new/rtl
./rtl/alu.sv
# a commented-out file
/proj/old/tb/env.sv
./rtl/top.svLine 3 changed. Line 8 did not. Now the same substitution with no address:
sed 's|/proj/old/|/proj/new/|g' filelists/top.f+define+DEBUG
# indented comment
+incdir+/proj/new/rtl
./rtl/alu.sv
# a commented-out file
/proj/new/tb/env.sv
./rtl/top.svLine 8 changed too, and the build now looks for a testbench in a directory that does not exist.
That is the whole argument for scoping. The two outputs differ by one line out of nine; both look like reasonable filelists; both exit 0. The address /+incdir+/ is not decoration — it is the statement that this edit belongs to include paths and nothing else.
4. A Block: Several Commands, One Region
An address governs one command. To govern several, wrap them in braces.
/start/,/end/{ command; command; }The registered example reduces a timing report — squeezing runs of whitespace and printing, but only inside a path block:
cat -n sta/timing.rpt 1 Startpoint: u_alu/rd_data_reg[3]
2 Endpoint: u_fifo/wr_ptr_reg[0]
3 data arrival time 1.842
4 data required time 2.100
5 slack (MET) 0.258
6 Startpoint: u_fifo/rd_ptr_reg[1]
7 Endpoint: u_alu/op_reg[2]
8 data arrival time 2.410
9 slack (VIOLATED) -0.110sed -n '/^Startpoint/,/^[[:space:]]*slack/{ s/[[:space:]]\{2,\}/ /g; p; }' sta/timing.rptStartpoint: 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.110Read the parts:
-nturns off auto-print, so onlypproduces output./^Startpoint/,/^[[:space:]]*slack/selects a path block.{ … }applies both commands to every line in that block.s/[[:space:]]\{2,\}/ /gcollapses every run of two or more spaces into one — the column padding.pprints the result.
Both paths appear, because the range reopened at line 6 exactly as Chapter 4.4 measured. On this file the two paths cover every line, so everything came through; on a report with headers and footers outside the blocks, those would be dropped.
The useful property is that the block is the unit of scoping. Adding a third command inside the braces extends the same region to it, with no risk of it quietly applying file-wide.
5. Blocks Nest
An inner address inside an outer one narrows further:
sed -n '/^Startpoint/,/slack/{ /arrival/{ s/^[[:space:]]*//; p; }; }' sta/timing.rptdata arrival time 1.842
data arrival time 2.410The outer range picks the path blocks; the inner /arrival/ picks the arrival-time lines within them; the two commands strip leading whitespace and print. Two lines out of nine.
That reads as a sentence: inside each timing path, on the arrival-time lines, strip the indent and print. The nesting is what lets you say all three things in one command instead of chaining three seds.
A negated address works on a block too:
sed -n '/^[[:space:]]*#/!{ /./p; }' filelists/top.f+define+DEBUG
+incdir+/proj/old/rtl
./rtl/alu.sv
/proj/old/tb/env.sv
./rtl/top.sv"On lines that are not comments, print the ones that have at least one character." Six lines — and note line 7, the whitespace-only line, survived: /./ means "contains any character", and a space is a character. If you wanted it gone, /[^[:space:]]/ is the address that says so. The same lesson as Chapter 5.2, arriving from a different direction.
6. Where the Syntax Bites
a, i and c eat the rest of the line. Carried over from Chapter 5.3, measured again because it is a composition problem:
printf 'x\n' | sed -e 'a\
TEXT; p'x
TEXT; pThe ; p is text, not a command. Any command you want after an a, i or c needs its own -e or its own line. This is the main reason multi-command scripts that include these three are written with repeated -e rather than semicolons.
A closing brace needs a separator before it. Write { s/a/b/; p; } with the trailing semicolon, as every example in this chapter does. Leaving it out works on some seds and not others, and the cost of the habit is one character.
Readability is a real concern. Compare:
sed -n '/^Startpoint/,/^[[:space:]]*slack/{ s/[[:space:]]\{2,\}/ /g; p; }' fwith the same thing across lines:
sed -n '/^Startpoint/,/^[[:space:]]*slack/{
s/[[:space:]]\{2,\}/ /g
p
}' fBoth run. The second is the one a colleague can review, and once a script reaches that size it is usually better off in a file with -f, where it can carry comments and show up in a diff.
7. Order Still Decides the Result
Composition brings back every ordering rule in Part II at once, and they interact.
d ends the cycle, so anything after it — including a whole block — never runs for a deleted line:
printf 'keep\ndrop\n' | sed -e '/drop/d' -e 's/drop/CHANGED/'keepThat makes deletes a natural first step: strip the noise, then let everything below operate only on lines you have kept. It also means a d in the middle of a long script makes every later command conditional, which is worth a comment in the script when it happens.
Each command sees the previous one's result, so two substitutions in a block compound exactly as Chapter 2.2's clock example showed. Inside braces this is easy to lose track of, because the commands are visually grouped and read like a set rather than a sequence. They are a sequence.
Scoping is how you stop an edit leaking. Section 3's one-address difference is the small version; the general rule is that an unaddressed command in a multi-command script applies to every line the script sees, including ones the rest of the script is about to discard.
8. Think It Through
1. Write a command that rewrites /proj/old/ to /proj/new/ only on +incdir+ lines, and explain why the address matters here.
sed '/+incdir+/ s|/proj/old/|/proj/new/|g' filelists/top.f. It matters because the same path string appears on a source-file line that is not moving — section 3 showed the unscoped version silently rewriting it too. The address is the statement that this edit belongs to include paths.
2. sed -n '/^Startpoint/,/slack/{ p; }' report.rpt on a report with four paths prints all four. How would you get only the first?
Not with a pattern range — it reopens, which Chapter 4.4 measured. Either end the range on a line number, use ,+N if the block has a fixed size, or find the boundary first with sed -n '/^Startpoint/=' and then address by number. A q after the block would also stop sed at the first one, though q is Module 6.
3. Why does sed -e '/^#/d' -e 's/old/new/' file behave differently from sed -e 's/old/new/' -e '/^#/d' file?
In the first, comment lines are deleted and the substitution never sees them. In the second, the substitution runs on every line including comments — and since those lines are deleted immediately afterwards, the visible output is usually identical. The difference shows up when the substitution has consequences beyond the deleted lines, and in wasted work. Deletes first is the better habit.
4. What is wrong with sed '/^vcs/a\ -full64; s/x/y/' run.sh?
The ; s/x/y/ is appended text, not a command — section 6 measured exactly this shape. The output will contain a line reading -full64; s/x/y/. Split it into two -e fragments.
5. You need to strip trailing whitespace, but only inside a block of a report. Write it.
sed '/^Startpoint/,/slack/ s/[[:space:]]*$//' report.rpt for the single command, or wrap it in braces if you are adding more. Note this runs per path block and will fire in every one of them, since the range reopens — which is what you want for whitespace hygiene and would not be for an edit that should happen once.
6. When should a sed script move out of the command line and into a -f file?
When it stops fitting on one readable line, when it needs comments, or when it will be run more than once — which is Chapter 2.3's argument. A script file can be reviewed in a diff and committed next to the thing it edits; a one-liner retyped from shell history cannot. Module 6 returns to this when scripts gain control flow.
9. Summary
- A sed script is a list of commands applied in order, written with repeated
-e, semicolons, a real newline, or a-ffile. All equivalent. - An address on a command limits which lines it touches.
/+incdir+/ s|…|…|gchanged one line where the unaddressed version changed two — and the second one was a bug. { }scopes a group of commands to one address, so adding a command to the group inherits the same region.- Blocks nest. An inner address inside an outer range reads as a sentence: inside each path, on arrival lines, strip and print.
- A negated address works on a block too — and
/./matches a whitespace-only line, because a space is a character. a,iandcswallow the rest of the line, soTEXT; pappends the; p. Give the next command its own-e.- Put a separator before the closing brace —
{ …; p; }— and break long scripts across lines. dends the cycle, so a block after it never runs for that line. Deletes belong first.- The review question for every command is "which lines does this run on". For an unaddressed command the answer is "all of them", and that should be a decision.
That closes Part II. You can now write the sed that most engineering work needs: a precise pattern, on a chosen set of lines, doing a controlled edit, composed with other commands and scoped to a region. Module 6 — Advanced sed Processing opens the stateful half of the tool: the hold space that survives between lines, a pattern space holding more than one line at a time, branching for real control flow, and the script files that all of it belongs in. It is canonical depth rather than a prerequisite — Parts III and IV do not assume it — but it is where sed stops being a line filter and becomes something closer to a small language.
Where this fits
Part of the sed curriculum.
