Some Line Commands

Learning SED

Chapter 5 — Append, Insert and Change

Adding and Replacing Lines

The substitute command (s) works within a line — it replaces matched text inside the pattern space. But sometimes you need to do something more structural: add a new line after a match, insert a line before it, or replace the entire line with something different. That is what a, i, and c are for.

Command Full name What it does When it outputs
a Append Adds one or more lines after the addressed line After the current line is printed at end of cycle
i Insert Adds one or more lines before the addressed line Immediately, before the pattern space is printed
c Change Replaces the addressed line(s) with new text In place of the addressed line; suppresses normal print

A crucial difference from s: the text supplied to a, i, and c is literal — it is not treated as a regular expression, and special characters like & and \1 have no meaning in it. What you type is exactly what gets output.

The Append Command: a

The a command queues text to be printed after the current line. The appended text does not become part of the pattern space — it is output directly after the current cycle's default print, without passing through any further SED commands.

Syntax — two forms

GNU sed accepts a concise single-line form:

sed 'a\text to append' file.txt
# Or without the backslash (GNU sed extension):
sed 'a text to append' file.txt

The POSIX-portable form uses a backslash-newline after the command letter, with the text on the following line:

sed 'a\
text to append' file.txt

In a script file (-f) the portable form is always safe and clearest:

# append.sed
a\
text to append

Appending after every line

# Add a blank line after every line (double-space a file)
sed 'a\\' file.txt

# Add a separator after every line
sed 'a\---' file.txt

Appending after a specific line

# Add a line after line 3
sed '3a\inserted after line 3' file.txt

# Add a line after the last line (append to end of file)
sed '$a\# End of configuration' config.txt

Appending after a pattern match

# Add a blank line after every section heading (lines starting with [)
sed '/^\[/a\\' config.ini
before [database] host=localhost port=5432 [cache] ttl=300
after sed '/^\[/a\\' [database]   host=localhost port=5432 [cache]   ttl=300
# Add a new directive after an existing one in a config file
sed '/^ServerName/a\ServerAlias www.example.com' httpd.conf

# Add a comment explaining the next line
sed '/^PermitRootLogin/i\# Disable root login for security' sshd_config
# (note: this uses i not a — see below)

The Insert Command: i

The i command works exactly like a but places text before the addressed line rather than after it. The inserted text is output immediately, before SED prints the pattern space at end of cycle.

Syntax

# GNU sed single-line form
sed 'i\text to insert' file.txt
sed 'i text to insert' file.txt

# POSIX portable form
sed 'i\
text to insert' file.txt

Inserting before a specific line

# Insert a header before line 1 (prepend to file)
sed '1i\# Generated by deploy.sh — do not edit manually' output.conf

# Insert a blank line before every line starting with "Chapter"
sed '/^Chapter/i\\' book.txt
before Introduction text. Chapter 1: Basics Content here. Chapter 2: Advanced More content.
after sed '/^Chapter/i\\' Introduction text.   Chapter 1: Basics Content here.   Chapter 2: Advanced More content.

Inserting before a pattern match

# Add a warning comment before any line that sets a dangerous option
sed '/PermitRootLogin yes/i\# WARNING: root login enabled — review before production' sshd_config

# Insert a CSS rule before an existing one
sed '/\.header {/i\.header-wrapper { width: 100%; }' styles.css

# Add a shebang line to a script that is missing one
sed '1i\#!/usr/bin/env bash' script.sh

The Change Command: c

The c command replaces the entire addressed line — or the entire addressed range — with new text. It suppresses the normal printing of the pattern space and outputs the replacement text instead.

This is fundamentally different from s: while s modifies text within a line, c throws the line away entirely and puts something new in its place.

Syntax

# GNU sed single-line form
sed '/pattern/c\replacement line' file.txt

# POSIX portable form
sed '/pattern/c\
replacement line' file.txt

Replacing a specific line by number

# Replace line 1 entirely
sed '1c\# New header line' file.txt

# Replace the last line
sed '$c\# End of file' file.txt

Replacing matched lines

# Replace any line containing "TODO" with a placeholder
sed '/TODO/c\# [REMOVED: TODO item]' notes.txt

# Completely replace a config directive with a known-good value
sed '/^MaxConnections/c\MaxConnections 100' server.conf
before ServerName example.com MaxConnections 9999 Timeout 300 MaxConnections 0
after sed '/^MaxConnections/c\MaxConnections 100' ServerName example.com MaxConnections 100 Timeout 300 MaxConnections 100
Every matching line is replaced independently. If the pattern matches five lines, each is replaced by the same c text, so the replacement text appears five times. This is different from a range address — see below.

Replacing a range with c

When c is applied to a range address, the behaviour changes: instead of replacing each line in the range individually, the entire range is replaced with the text just once — output when the last line of the range is processed.

# Replace lines 3 through 7 with a single placeholder line
sed '3,7c\[section removed]' file.txt
before line 1 line 2 line 3 line 4 line 5 line 6
after sed '3,5c\[removed]' line 1 line 2 [removed] line 6
# Replace an entire config block with a new version
sed '/^# BEGIN SSL/,/^# END SSL/c\# SSL configured via include — see ssl.conf' httpd.conf
Range + pattern address quirk with c: When using a pattern range (/start/,/end/) with c, the single-output behaviour applies only for line-number ranges. With pattern ranges, each line in the matching range is still processed individually by c (outputting the replacement text for each line). Use d + a together if you need a true block replacement with a pattern range.

Multi-line Text Blocks

All three commands — a, i, and c — can output multiple lines. In the multi-line form, each line except the last ends with a backslash:

# Append a two-line block after line 1
sed '1a\
# ======================\
# Auto-generated config\
# Do not edit manually' config.txt

# Insert a multi-line licence header before line 1
sed '1i\
# Copyright 2025 Example Corp\
# Licensed under the MIT Licence\
#' script.sh

# Replace a line with a multi-line block
sed '/^PLACEHOLDER/c\
# Line one of replacement\
# Line two of replacement\
# Line three of replacement' template.txt
For large multi-line insertions in scripts, use a here-doc approach instead. Feeding a multi-line SED script via -f is far more readable than a shell one-liner when you are inserting five or more lines.

Comparing a, i, c and s

Command Operates on Text is literal? Pattern space changed? Output position
s Text within the current line No — replacement has special chars (&, \1) Yes — match is replaced in-place At end of cycle (normal print)
a A new line added after the current one Yes — completely literal No After the current line's normal print
i A new line added before the current one Yes — completely literal No Before the current line's normal print
c The entire current line (or range) Yes — completely literal Yes — pattern space deleted Replaces the normal print

Practical Recipes

Add a file header and footer

# Prepend a header and append a footer to any file
sed -e '1i\# AUTO-GENERATED — DO NOT EDIT' \
    -e '$a\# END OF FILE' output.conf

Insert a new config directive after an existing one

# Add "AllowOverride All" after every "DocumentRoot" line
sed '/DocumentRoot/a\    AllowOverride All' httpd.conf

# Add "Restart=on-failure" after "Type=simple" in a systemd unit
sed '/^Type=simple/a\Restart=on-failure' myservice.service

Replace a placeholder in a template

# template.conf contains the line: ##DB_CONFIG_PLACEHOLDER##
# Replace it with the real database block
sed '/^##DB_CONFIG_PLACEHOLDER##/c\
db_host=db.internal\
db_port=5432\
db_name=production\
db_user=app' template.conf

Add blank lines around every heading in a Markdown file

# Insert blank line before and append blank line after every ## heading
sed '/^## / {
  i\\
  a\\
}' README.md

Wrap each line of a file in XML tags

# Transform each line into <item>line content</item>
# s// handles the wrapping inline — but a and i can add surrounding tags
sed 's/.*\/<item>&<\/item>/' items.txt
# Or using i and a for a different structure:
sed -e '1i\<items>' \
    -e 's/.*\/  <item>&<\/item>/' \
    -e '$a\<\/items>' items.txt

Safely replace a known-bad config value

# Use c rather than s when the entire line should be replaced
# — avoids accidentally matching part of a value
sed '/^net.ipv4.ip_forward/c\net.ipv4.ip_forward = 1' /etc/sysctl.conf

Add a redirect rule to an nginx config

# Insert a return 301 after the server_name directive
sed '/server_name old-domain.com/a\    return 301 https://new-domain.com$request_uri;' nginx.conf

Remove a block and replace with a comment

# Between BEGIN LEGACY and END LEGACY, replace the whole block
sed '/# BEGIN LEGACY/,/# END LEGACY/ {
  /# BEGIN LEGACY/c\# LEGACY CODE REMOVED — see migration guide
  /# BEGIN LEGACY/!d
}' script.sh
# Explanation:
#  On the BEGIN line: c replaces it with the comment
#  On all other lines in the range: d deletes them

Portability Notes

Feature GNU sed (Linux) BSD sed (macOS)
a text (no backslash) Works Requires a\ with newline
i text (no backslash) Works Requires i\ with newline
c text (no backslash) Works Requires c\ with newline
Range address with c Outputs replacement once for entire range Outputs replacement once per line in range
Multi-line text with \ Works Works
Most portable style: Always use the backslash-newline form in shell scripts or -f files that need to run on both Linux and macOS:
sed '/pattern/a\
text to append' file.txt
This works on GNU sed, BSD sed, and all POSIX-compliant implementations without modification.

Quick Reference — Chapter 5

Append — a

a\text Append text after every line
Na\text Append text after line N
/pat/a\text Append text after every line matching pat
$a\text Append text after the last line (add to end of file)

Insert — i

i\text Insert text before every line
1i\text Insert text before line 1 (prepend to file)
/pat/i\text Insert text before every line matching pat

Change — c

/pat/c\text Replace every line matching pat with text
n,mc\text Replace lines n through m with a single text line
Nc\text Replace line N entirely with text

Multi-line text (all three commands)

a\line one\
         line two
Each line except the last ends with \ to continue
What is coming next: Chapter 6 introduces the hold space — SED's second buffer that persists between processing cycles. It is the key to solving problems that require memory of a previous line, such as reversing a file, deduplicating, or processing header–body–footer structures. This is where SED moves from a line-by-line tool to something genuinely powerful.