OS: Linux sed – Stream Editor

sed (stream editor) is a non-interactive command-line tool that reads text line by line, applies editing commands, and writes the result to standard output. It's ideal for substitution, deletion, insertion, and text transformation in scripts and pipelines.

What is `sed`?

sed (stream editor) processes text one line at a time. It reads input (a file or stdin), applies a sequence of editing commands, and prints the result — all without opening the file in a traditional editor.

It is irreplaceable for:

  • Bulk find-and-replace across files
  • Deleting or extracting specific lines
  • Transforming log output or config files in pipelines

Basic Syntax

bash
sed [OPTIONS] 'SCRIPT' [FILE...]

  • SCRIPT — one or more editing commands (usually quoted with '...')
  • FILE — one or more input files; if omitted, reads from stdin

The Substitution Command `s`

The substitution command is by far the most common use of sed.

text
s/PATTERN/REPLACEMENT/FLAGS

  • PATTERN → a regular expression to match
  • REPLACEMENT → the text to put in place of the match
  • FLAGS → optional modifiers (see below)

Replace First Match Per Line

bash
sed 's/foo/bar/' file.txt

→ Replaces the first occurrence of foo on each line with bar.

Replace All Matches Per Line (`g` flag)

bash
sed 's/foo/bar/g' file.txt

→ Replaces every occurrence on each line.

Case-Insensitive Match (`I` flag)

bash
sed 's/error/ERROR/Ig' log.txt

→ Matches error, Error, ERROR, etc.

Replace Only the Nth Occurrence (`N` flag)

bash
sed 's/foo/bar/2' file.txt

→ Replaces only the 2nd occurrence on each line.


Editing Files In Place (`-i`)

By default sed prints to stdout and leaves the original file untouched. The -i flag overwrites the file in place.

bash
sed -i 's/localhost/127.0.0.1/g' config.conf

Always make a backup first:

bash
sed -i.bak 's/localhost/127.0.0.1/g' config.conf

→ Saves the original as config.conf.bak before modifying.

**macOS note**: BSD `sed` requires an explicit suffix with `-i`. Use `-i ''` for no backup, or `-i.bak` for a backup.

Changing the Delimiter

The default delimiter is /. When your pattern or replacement contains slashes, swap to another character to avoid escaping.

bash
# Replace a file path — using | instead of /
sed 's|/var/log|/logs|g' app.conf

# Same with # as delimiter
sed 's#http://old.example.com#https://new.example.com#g' links.txt

Any character can be used as the delimiter: |, #, @, ,, etc.


Addressing — Target Specific Lines

By default, sed applies commands to every line. An address before the command restricts it.

By Line Number

bash
# Edit only line 5
sed '5s/foo/bar/' file.txt

# Edit lines 2 through 10
sed '2,10s/foo/bar/' file.txt

# Edit the last line
sed '$s/foo/bar/' file.txt

By Regex Pattern

bash
# Edit only lines containing "ERROR"
sed '/ERROR/s/timeout/Timed Out/g' app.log

By Range (start/end pattern)

bash
# Edit lines between START and END markers (inclusive)
sed '/START/,/END/s/foo/bar/g' file.txt

Negating an Address (`!`)

bash
# Edit every line EXCEPT those matching "DEBUG"
sed '/DEBUG/!s/foo/bar/g' file.txt


Deleting Lines (`d`)

bash
# Delete blank lines
sed '/^$/d' notes.txt

# Delete lines containing "TODO"
sed '/TODO/d' source.c

# Delete lines 1 through 5
sed '1,5d' file.txt

# Delete the last line
sed '$d' file.txt


Printing Specific Lines (`p` with `-n`)

By default sed prints every line. Use -n to suppress output and p to print only what you want.

bash
# Print only lines 10 to 20
sed -n '10,20p' file.txt

# Print only lines matching a pattern
sed -n '/ERROR/p' app.log

# Print line numbers along with matching lines
sed -n '/WARN/{=;p}' app.log

→ = prints the current line number.


Inserting and Appending Lines

Insert Before a Line (`i`)

bash
# Insert a comment before line 3
sed '3i\# This section is auto-generated' config.cfg

# Insert before every line matching a pattern
sed '/\[section\]/i\# --- section start ---' config.ini

Append After a Line (`a`)

bash
# Append a blank line after every line matching "END"
sed '/END/a\\' file.txt

# Append a footer after the last line
sed '$a\# End of file' script.sh

Replace an Entire Line (`c`)

bash
# Replace line 7 entirely
sed '7c\replaced content here' file.txt


Transliterate Characters (`y`)

y is like tr — it maps characters one-to-one.

bash
# Convert lowercase a-z to uppercase A-Z
sed 'y/abcdefghijklmnopqrstuvwxyz/ABCDEFGHIJKLMNOPQRSTUVWXYZ/' file.txt


Multiple Commands

Semicolon-Separated

bash
sed 's/foo/bar/g; s/baz/qux/g' file.txt

`-e` Flag (multiple `-e` options)

bash
sed -e 's/foo/bar/g' -e '/^#/d' file.txt

Script File (`-f`)

For complex scripts, save commands in a file and reference it:

bash
# cleanup.sed
s/foo/bar/g
/^$/d
s/  */ /g

sed -f cleanup.sed input.txt


Hold Space — Advanced Usage

sed has two buffers:

  • Pattern space — the current line being processed
  • Hold space — a secondary buffer you can save/restore
CommandMeaning
hCopy pattern → hold
HAppend pattern → hold
gCopy hold → pattern
GAppend hold → pattern
xExchange pattern and hold

bash
# Reverse the order of two lines
sed -n '1{h;d}; 2{G;p}' file.txt


Common Real-World Examples

Remove all comments from a config file:

bash
sed '/^\s*#/d' nginx.conf

Strip trailing whitespace from every line:

bash
sed 's/[[:space:]]*$//' file.txt

Add line numbers as a prefix:

bash
sed '=' file.txt | sed 'N; s/\n/\t/'

Extract lines between two markers:

bash
sed -n '/BEGIN CERTIFICATE/,/END CERTIFICATE/p' server.crt

In-place replace across multiple files:

bash
sed -i.bak 's/v1\.0/v2\.0/g' README.md CHANGELOG.md docs/*.md

Remove ANSI color codes from a log:

bash
sed 's/\x1b\[[0-9;]*m//g' colored.log


sed in Pipelines

sed is designed for pipelines — it reads from stdin and writes to stdout.

bash
# Filter only ERROR lines then normalize the word "timeout"
grep 'ERROR' app.log | sed 's/timeout/Timed Out/g'

# Replace placeholders in a template and feed to a mailer
sed "s/{{NAME}}/Alice/g; s/{{DATE}}/$(date +%F)/g" template.txt | mail -s "Report" alice@example.com

# Extract HTTP status codes from an access log
awk '{print $9}' access.log | sort | uniq -c | sort -nr


Quick Reference

TaskCommand
Replace first matchsed 's/old/new/'
Replace all matchessed 's/old/new/g'
Edit in place + backupsed -i.bak 's/old/new/g' file
Delete blank linessed '/^$/d'
Delete lines matching regexsed '/regex/d'
Print lines 5–15sed -n '5,15p' file
Print lines matching patternsed -n '/pattern/p' file
Insert line before matchsed '/match/i\new line'
Append line after matchsed '/match/a\new line'
Change delimiter`sed 's\/old/path\/new/path\g'`
Run multiple commandssed -e 'cmd1' -e 'cmd2'

Gotchas

  • sed -i without a backup overwrites permanently — test with sed '...' file.txt first.
  • Extended regex (-E or -r) unlocks +, ?, |, () without escaping — standard sed needs \+, \?, \|, \(, \).
  • Greedy matching — . matches as much as possible; use [^X] to stop at a character.
  • macOS vs GNU sed — use sed -E on macOS and sed -r on Linux for extended regex, or install GNU sed via Homebrew (gsed).
  • In-place with no backup on macOS requires sed -i '' (empty string argument), not just sed -i.