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
# 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
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
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
# Replace an entire config block with a new version sed '/^# BEGIN SSL/,/^# END SSL/c\# SSL configured via include — see ssl.conf' httpd.conf
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
-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 |
-f files that need to run on both Linux and macOS:
sed '/pattern/a\ text to append' file.txtThis works on GNU sed, BSD sed, and all POSIX-compliant implementations without modification.
Quick Reference — Chapter 5
Append — a
Insert — i
Change — c
Multi-line text (all three commands)
line two Each line except the last ends with
\ to continue