Skip to main content

The Linux sed Command: Stream Editing from the Command Line

·1343 words·7 mins
Linux Learning Lab
Author
Linux Learning Lab
Writing about code, tools, and workflows.

What Is sed?
#

sed (stream editor) reads input line by line, applies editing commands, and writes the result to standard output. It never opens a file in an editor — it processes text as a stream, which makes it ideal for scripting, automation, and quick transformations from the command line.

sed 'command' file

By default, sed prints every line of input (modified or not) to stdout. The original file is unchanged unless you use the -i flag.

Basic Substitution
#

The most common use of sed is find-and-replace with the s (substitute) command:

sed 's/old/new/' file.txt

This replaces the first occurrence of old on each line. To replace all occurrences on a line, add the g (global) flag:

sed 's/old/new/g' file.txt

Flags for Substitution
#

FlagMeaning
gReplace all occurrences on the line
iCase-insensitive match (GNU sed)
pPrint the modified line (useful with -n)
w fileWrite matched lines to a file
2Replace only the 2nd occurrence

Examples:

# Case-insensitive replacement
sed 's/error/WARNING/gi' app.log

# Replace only the second occurrence on each line
sed 's/foo/bar/2' data.txt

# Print only lines where a substitution was made
sed -n 's/DEBUG/INFO/p' app.log

Alternate Delimiters
#

When your pattern contains /, use a different delimiter to avoid escaping:

# These are equivalent:
sed 's/\/usr\/local\/bin/\/opt\/bin/' config.txt
sed 's|/usr/local/bin|/opt/bin|' config.txt
sed 's#/usr/local/bin#/opt/bin#' config.txt

Any character can serve as the delimiter. Pick one that doesn’t appear in your pattern.

In-Place Editing with -i
#

To modify a file directly instead of printing to stdout:

sed -i 's/old/new/g' config.conf

Create a backup before modifying:

sed -i.bak 's/old/new/g' config.conf

This saves the original as config.conf.bak and writes changes to config.conf.

Caution: -i with no backup is destructive. On macOS (BSD sed), -i requires an argument — use -i '' for no backup or -i .bak for a backup.

Addressing: Targeting Specific Lines
#

By default, sed commands apply to every line. Addresses let you target specific lines:

Line Numbers
#

# Only line 5
sed '5s/foo/bar/' file.txt

# Lines 10 through 20
sed '10,20s/foo/bar/' file.txt

# From line 3 to the end of the file
sed '3,$s/foo/bar/' file.txt

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

Pattern Addresses
#

Target lines matching a regular expression:

# Lines containing "error"
sed '/error/s/foo/bar/' log.txt

# Lines starting with a comment
sed '/^#/s/old/new/' config.conf

# Lines between two patterns (inclusive)
sed '/START/,/END/s/foo/bar/' file.txt

Negation
#

Use ! to invert the address — apply the command to lines that don’t match:

# Replace on all lines EXCEPT those containing "keep"
sed '/keep/!s/old/new/g' file.txt

# Delete all blank lines
sed '/^$/d' file.txt

# Delete all lines that are NOT blank (inverted)
sed '/^$/!d' file.txt

Deleting Lines
#

The d command removes lines from the output:

# Delete line 3
sed '3d' file.txt

# Delete lines 10 through 20
sed '10,20d' file.txt

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

# Delete lines containing "DEBUG"
sed '/DEBUG/d' app.log

# Delete comment lines and blank lines
sed '/^#/d; /^$/d' config.conf

Inserting and Appending Lines
#

Insert Before a Line
#

# Insert a line before line 3
sed '3i\This line was inserted' file.txt

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

Append After a Line
#

# Append after line 5
sed '5a\This line was appended' file.txt

# Append after lines matching a pattern
sed '/^server {/a\    # Added by automation' nginx.conf

Replace an Entire Line
#

The c command replaces the entire matched line:

# Replace line 3 entirely
sed '3c\This replaces the entire line' file.txt

# Replace lines matching a pattern
sed '/^hostname=/c\hostname=newserver.example.com' config.conf

Printing Specific Lines
#

Use -n to suppress default output, then p to print only what you want:

# Print only line 5
sed -n '5p' file.txt

# Print lines 10 through 20
sed -n '10,20p' file.txt

# Print lines containing "error"
sed -n '/error/p' log.txt

# Print from a pattern to end of file
sed -n '/START/,$p' file.txt

This makes sed work like a targeted grep with line-range awareness.

Multiple Commands
#

Semicolons
#

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

The -e Flag
#

sed -e 's/foo/bar/g' -e 's/baz/qux/g' file.txt

Script Files
#

For complex edits, put commands in a file:

# edits.sed
s/foo/bar/g
s/baz/qux/g
/^#/d
sed -f edits.sed file.txt

Regular Expressions in sed
#

sed uses Basic Regular Expressions (BRE) by default. Use -E (or -r on older systems) for Extended Regular Expressions (ERE):

# BRE: escape grouping and quantifiers
sed 's/\(error\|warn\)/ALERT/' log.txt

# ERE: cleaner syntax with -E
sed -E 's/(error|warn)/ALERT/' log.txt

Common Patterns
#

# Remove leading whitespace
sed 's/^[[:space:]]*//' file.txt

# Remove trailing whitespace
sed 's/[[:space:]]*$//' file.txt

# Remove both leading and trailing whitespace
sed 's/^[[:space:]]*//; s/[[:space:]]*$//' file.txt

# Remove HTML tags
sed -E 's/<[^>]+>//g' page.html

# Extract text between quotes
sed -E 's/.*"([^"]+)".*/\1/' file.txt

Capture Groups and Back-References
#

Captured groups are referenced with \1, \2, etc.:

# Swap first and last name
sed -E 's/^([A-Za-z]+) ([A-Za-z]+)/\2 \1/' names.txt

# Add quotes around each word
sed -E 's/([^ ]+)/"\1"/g' words.txt

# Reformat dates from MM/DD/YYYY to YYYY-MM-DD
sed -E 's|([0-9]{2})/([0-9]{2})/([0-9]{4})|\3-\1-\2|g' dates.txt

Practical Examples
#

Edit Configuration Files
#

# Change a port number
sed -i 's/^port=8080/port=9090/' app.conf

# Uncomment a line
sed -i 's/^#ListenAddress/ListenAddress/' /etc/ssh/sshd_config

# Comment out a line
sed -i '/^PermitRootLogin/s/^/#/' /etc/ssh/sshd_config

Process Log Files
#

# Remove timestamps from log lines
sed -E 's/^[0-9]{4}-[0-9]{2}-[0-9]{2} [0-9:]+//' app.log

# Extract only ERROR lines and strip the level prefix
sed -n '/ERROR/s/.*ERROR //p' app.log

# Replace IP addresses with [REDACTED]
sed -E 's/[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}/[REDACTED]/g' access.log

Bulk File Edits
#

# Replace across multiple files
sed -i 's/oldfunction/newfunction/g' src/*.py

# Add a header to every .sh file
sed -i '1i\#!/bin/bash' scripts/*.sh

# Remove Windows carriage returns
sed -i 's/\r$//' script.sh

Work with Structured Data
#

# Extract values from KEY=VALUE files
sed -n 's/^DB_HOST=//p' .env

# Change a specific key's value
sed -i 's/^DB_HOST=.*/DB_HOST=newdb.example.com/' .env

# Add a line after a specific section header
sed -i '/^\[database\]/a\connection_timeout=30' config.ini

Combining sed with Other Commands
#

# Transform output from another command
ps aux | sed -n '/nginx/p'

# Use in a pipeline to clean data
cat data.csv | sed '1d' | sed 's/,/\t/g' > data.tsv

# Modify command output before saving
kubectl get pods | sed '1d' | sed 's/  */,/g' > pods.csv

sed vs. Other Tools
#

TaskBest toolWhy
Simple find/replace in filessedFast, scriptable, handles in-place edits
Pattern matching and printinggrepSimpler syntax, better for filtering
Field-based text processingawkColumn awareness built in
Complex multi-line transformsawk or a scriptsed multi-line support is awkward
Interactive find/replaceYour editorVisual confirmation

Common Pitfalls
#

Forgetting -i modifies the file. Always test without -i first:

# Preview the changes
sed 's/old/new/g' file.txt

# Then apply
sed -i 's/old/new/g' file.txt

Greedy matching. .* matches as much as possible:

# This removes everything between the FIRST < and LAST >
echo "<a>text</a>" | sed 's/<.*>//'
# Output: (empty)

# Use negated character class instead
echo "<a>text</a>" | sed 's/<[^>]*>//g'
# Output: text

Unescaped special characters. In BRE, these need escaping: (, ), {, }, +, ?, |. In ERE (-E), they don’t.

macOS vs. Linux. BSD sed (macOS) differs from GNU sed (Linux) in:

  • -i requires an argument on macOS (-i '')
  • Some flags like \t for tab don’t work — use a literal tab or $'\t'
  • -E works on both, but -r is GNU-only

Quick Reference
#

CommandWhat it does
s/old/new/Replace first occurrence
s/old/new/gReplace all occurrences
dDelete the line
pPrint the line
i\textInsert text before the line
a\textAppend text after the line
c\textReplace the entire line
qQuit (stop processing)
=Print the line number
AddressWhat it matches
5Line 5
5,10Lines 5 through 10
$Last line
/pattern/Lines matching the pattern
/start/,/end/Range between two patterns
!Negate (invert the match)
FlagWhat it does
-nSuppress automatic printing
-iEdit file in place
-i.bakEdit in place, keep backup
-EUse extended regular expressions
-eAdd a command
-f fileRead commands from a file