Skip to content
VLSI Mentor

sed Part II · Module 5 · Filtering and Line Operations

Inserting, Appending, and Changing Text

Every command so far has worked on text that was already in the file. These three create it: i puts a line before the matched one, a puts a line after it, and c replaces it. That makes them the right tool for a generated-file header, an extra flag after a matched option, or swapping out a whole configuration line. They are also where GNU and BSD sed disagree most sharply — the convenient one-line form you will find in most examples online is a GNU extension that fails outright on a Mac. And all three behave unlike anything else in sed in a way worth knowing: the text they produce never enters the pattern space, so the rest of your script cannot see it.

Intermediate19 min readsedPart IIPortabilityFilelistsTool Scripts

Part II · Module 5 · Chapter 5.3 · Inserting, Appending, and Changing Text

1. Three Commands That Make Lines

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
   i    insert — put text BEFORE the addressed line
   a    append — put text AFTER the addressed line
   c    change — REPLACE the addressed line with text

They work on whole lines. There is no "insert in the middle of a line" — that is what s is for. If you want to add text to an existing line, Chapter 3.3's & is the tool; if you want a new line in the output, these are.

Typical jobs:

  • i — a // AUTOGENERATED banner at the top of a file your flow writes.
  • a — an extra compile option on the line after a matched tool invocation.
  • c — replacing a whole set link_library line with the new release's value.

2. The Form You Will See Online

Most examples on the internet are written like this:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
   sed '1i // AUTOGENERATED'        put the text right after the command letter

That is the GNU one-line form, and it is genuinely convenient. It is also a GNU extension.

3. Why Those Examples Fail on a Mac

All three of this chapter's registered examples use that form. Here they are, run as written:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
sed '1i // AUTOGENERATED - do not edit' filelists/top.f
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
sed: 1: "1i // AUTOGENERATED - d ...": command i expects \ followed by text
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
sed '/^vcs /a -full64' sim/run.sh
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
sed: 1: "/^vcs /a -full64
": command a expects \ followed by text
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
sed '/^set link_library/c set link_library { * std.db }' synth/dc.tcl
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
sed: 1: "/^set link_library/c se ...": command c expects \ followed by text

All three exit with status 1. The message is unusually helpful: this command expects a backslash followed by the text.

This is the good kind of portability failure — loud, immediate, and it tells you the fix. Compare it with the silent ones this track has measured: GNU's \U emitting a literal U (Chapter 3.3), \| matching nothing (Chapter 4.1), and 0,/re/ returning nothing (Chapter 4.4). Those three run happily and give you wrong answers. These stop.

4. The Form That Works Everywhere

POSIX spells it with a backslash, a real newline, and then the text:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
   sed '1i\
   TEXT'

The newline inside the quotes is mandatory — that is what the error message was asking for. In a shell script this means the command spans two lines, which looks odd the first time and is completely normal.

Inserting a header:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
sed '1i\
// AUTOGENERATED - do not edit' filelists/top.f
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// AUTOGENERATED - do not edit
+define+DEBUG
  # indented comment
+incdir+/proj/old/rtl

./rtl/alu.sv
# a commented-out file
   
/proj/old/tb/env.sv
./rtl/top.sv

The header is on top and the original nine lines follow, unchanged.

Appending an option after a matched tool line:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
cat -n sim/run.sh
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
     1	#!/bin/sh
     2	vcs -sverilog -timescale=1ns/1ps top.sv
     3	./simv +UVM_TESTNAME=smoke
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
sed '/^vcs /a\
  -full64' sim/run.sh
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
#!/bin/sh
vcs -sverilog -timescale=1ns/1ps top.sv
  -full64
./simv +UVM_TESTNAME=smoke

The new line landed after the vcs line. Note the two leading spaces survived — on the sed used here, leading whitespace in the text is preserved, which cat -et confirms:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
printf 'x\n' | sed 'a\
    indented' | cat -et
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
x$
    indented$

(Some older seds strip leading whitespace from a and i text unless you escape the first space with a backslash. If your indentation disappears on another machine, \ indented is the defensive spelling.)

Changing a whole line:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
cat -n synth/dc.tcl
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
     1	set search_path { /libs/v1 }
     2	set link_library { * sc_tt.db }
     3	set target_library { sc_tt.db }
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
sed '/^set link_library/c\
set link_library { * std.db }' synth/dc.tcl
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
set search_path { /libs/v1 }
set link_library { * std.db }
set target_library { sc_tt.db }

Line 2 replaced entirely. c is the right choice over s when the whole line is being rewritten — you state the new line once, instead of writing a pattern that has to match the old one exactly.

Several lines at once. End each line of text with a backslash except the last:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
sed '1i\
// AUTOGENERATED\
// do not edit' filelists/top.f | head -4
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// AUTOGENERATED
// do not edit
+define+DEBUG
  # indented comment

5. c on a Range Collapses the Whole Range

i and a behave the same whether their address is one line or many. c does not, and the difference is large.

With a plain pattern address, c replaces each matching line individually:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
cat -n sta/timing.rpt
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
     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.110
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
sed '/^Endpoint/c\
[ENDPOINT]' sta/timing.rpt
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
Startpoint: u_alu/rd_data_reg[3]
[ENDPOINT]
  data arrival time                        1.842
  data required time                       2.100
  slack (MET)                              0.258
Startpoint: u_fifo/rd_ptr_reg[1]
[ENDPOINT]
  data arrival time                        2.410
  slack (VIOLATED)                        -0.110

Nine lines in, nine lines out — each Endpoint line swapped for the replacement.

With a range, c replaces the entire range with a single copy of the text:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
sed '/^Startpoint/,/slack/c\
[PATH REMOVED]' sta/timing.rpt
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
[PATH REMOVED]
[PATH REMOVED]

Nine lines in, two out. The first path block, lines 1 to 5, became one line. Then — exactly as Chapter 4.4 described — the range reopened on line 6 and the second block, lines 6 to 9, became the other.

That is a genuinely useful operation when you mean it: "replace each timing path with a one-line marker" is a real log-reduction task. It is also a severe surprise if you expected c to work line by line. The rule:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
   /pat/c\TEXT          one replacement per matching line
   /a/,/b/c\TEXT        one replacement per RANGE OCCURRENCE

6. The Text Never Enters the Pattern Space

This is the behaviour that makes i, a and c different from everything else in sed, and nothing else in the module predicts it.

Text from these commands is queued for output directly. It is not put into the pattern space, so no later command in your script can match it, change it, or delete it.

Append a flag, then try to rewrite it:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
printf 'vcs top.sv\n' | sed -e 'a\
-full64' -e 's/-full64/XXX/'
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
vcs top.sv
-full64

The substitution did nothing. It ran against the pattern space, which still held vcs top.sv — the appended text was never there to be matched. On GNU or BSD alike, appended text is output, not buffer.

The same property in the other direction: i writes its text immediately, and a later d cannot take it back:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
printf 'x\ny\n' | sed -e '/x/i\
BEFORE-X' -e '/x/d'
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
BEFORE-X
y

BEFORE-X was inserted, then the x line itself was deleted — and the inserted line stayed. The insert had already happened; d ends the cycle for the pattern space, not for output that has already been emitted.

7. One Syntax Trap

With the backslash form, the text runs to the end of the line — including anything that looks like another command:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
printf 'x\n' | sed -e 'a\
TEXT; p'
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
x
TEXT; p

The ; p became part of the appended text rather than a second command. That is correct behaviour and it catches people who have got used to chaining commands with semicolons.

The fix is to give the next command its own -e, or its own line. Chapter 5.4 is about composing multi-command scripts and returns to this.

r filename reads an entire file in and queues it for output after the addressed line — a for a whole file rather than a line of text. It shares the queueing behaviour from section 6 exactly, so the file's contents are likewise invisible to the rest of the script. It is not part of this chapter's scope; it appears where a flow needs it, in Module 8's composition work.

9. Think It Through

1. A colleague's script has sed '1i // header' file and it fails on your Mac but works on their Linux box. What exactly do you change?

Replace the one-line form with the POSIX backslash-newline form:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
sed '1i\
// header' file

The error message — command i expects \ followed by text — is telling you precisely this. Note the newline must be real; 1i\\// header on one line is not the same thing.

2. When should you use c rather than s?

When the whole line is being replaced. c states the new line once; s requires a pattern that matches the old line, which you then have to keep in step with reality. For set link_library { * sc_tt.db } becoming set link_library { * std.db }, c addressed by /^set link_library/ keeps working when the old value changes, while an s that spells out the old value stops matching the day someone edits it.

3. You run sed '/^Startpoint/,/slack/c\…' on a report with four timing paths. How many replacement lines do you get?

Four — one per range occurrence, not one for the file and not one per input line. The range reopens at each Startpoint, and each occurrence collapses to a single copy of the text. Section 5 measured two from a two-path report.

4. Why can a later substitution not rewrite text you appended?

Because appended text never reaches the pattern space — section 6 measured it. The substitution runs against the input line, which does not contain the appended text at all. To change it, change the text in the a command; to post-process it, pipe into a second sed, where the appended line arrives as ordinary input.

While testing this, one more portability note surfaced. The -e 'a\' -e 'TEXT' split form, often recommended as a way to keep the command on one shell line, does not work on the sed used here:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
printf 'vcs top.sv\n' | sed -e 'a\' -e 'NEW'
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
sed: 1: "NEW
": extra characters at the end of N command

Exit 1 — the second -e was parsed as an N command rather than as the append text. Use a real newline inside a single -e, as section 4 does.

5. You want a header inserted only if the file does not already have one. Can sed do that?

Not in one pass, because deciding requires knowing something about a line you have not read yet — and Chapter 2.1 established that sed holds one line at a time with no memory between them. The practical answers are outside sed: test first with grep -q, and insert only if it is absent, or make the operation idempotent by deleting any existing header before inserting the new one. Module 6's hold space can carry state, but a grep -q guard is the honest answer for this.

6. Does a put the text after the current line, or at the end of the file?

After the current line — the one its address selected. $a\ is how you get "at the end of the file", because $ is the address for the last line. Without an address, a appends after every line, which is occasionally what you want and more often a surprise.

10. Summary

  • i inserts before, a appends after, c replaces — all three operate on whole lines, never part of one.
  • The one-line form sed '1i text' is a GNU extension. On BSD sed all three commands fail with command X expects \ followed by text, exit 1 — a loud, self-documenting failure, unlike the silent GNU-isms in Chapters 3.3, 4.1 and 4.4.
  • The portable form is a backslash then a real newline then the text. Continue lines with a trailing backslash for multi-line text.
  • Leading whitespace in the text is preserved on the sed used here; escape the first space if you need it defensively elsewhere.
  • c on a plain address replaces each matching line; c on a range replaces the whole range with one line — a nine-line two-path report became two lines, because the range reopened once.
  • The text never enters the pattern space. a\ -full64 followed by s/-full64/XXX/ left the appended line untouched, and an i survived a later d on the same line.
  • With the backslash form, the text runs to end of line — TEXT; p appended the semicolon and the p as literal text.
  • c beats s when the whole line changes, because you state the new line instead of describing the old one.

You now have the full line-level command set: print, delete, insert, append, change, substitute. What is left is putting several of them in one script and controlling where each one applies — which matters more than it sounds, because a substitution that is correct inside one region of a report is usually wrong outside it. Chapter 5.4 — Combining Commands and Scoped Operations closes Module 5 with { } blocks and the address scoping that makes a multi-command script safe.

Where this fits

Part of the sed curriculum.