sed — Stream Editor Reference

Quote

“Easy things should be easy, and hard things should be possible.”

Larry Wall, Programming Perl (1991)

sed is most useful when the unit of work is a line, a bounded range, or a regex-addressed slice of a file. This note keeps the high-value command patterns, documents GNU vs BSD/macOS differences, and pairs retained Bash/Linux and PowerShell demos with live output.

How sed works

sed reads one line into pattern space, applies every matching command, prints the result unless output is suppressed, and then starts the next cycle. That execution model explains most sed behavior, including why -n matters and why multiline commands such as N change the shape of the current pattern space.

flowchart TD
    A[Read one line into pattern space] --> B[Apply matching commands]
    B --> C{Quiet mode active?}
    C -->|No| D[Print pattern space]
    C -->|Yes| E[Print only when a command requests it]
    D --> F[Clear pattern space]
    E --> F
    F --> G{More input?}
    G -->|Yes| A
    G -->|No| H[Exit]

The two working buffers are below.

BufferPurpose
Pattern spaceThe current line or multiline chunk being edited
Hold spaceA persistent scratch buffer used across cycles

These invocation forms cover most day-to-day work.

FormMeaning
sed 's/old/new/' fileReplace the first match per line and print to stdout
sed -n '5,10p' filePrint only selected lines
sed -e 'cmd1' -e 'cmd2' fileChain multiple commands
sed -f script.sed fileRead commands from a script file
sed -i.bak 's/old/new/' fileEdit in place and keep a backup
`sed -E ‘s/(ab)/c/’ file`
sed -z 's/\n/,/g' fileGNU null-delimited mode for NUL-separated input

These address forms decide which lines a command sees.

AddressMeaning
5Line 5 only
$Last line
/pattern/Lines that match the regex
5,10Lines 5 through 10
/start/,/end/Range from the first start match through the next end match
addr!Every line not matched by the address
1~2Every odd line on GNU sed

Substitution and regex

Substitution is sed’s core feature. The examples below show how default replacement scope, delimiters, and regex dialect change the result.

Bash/Linux

Replace the first match on each line

Without g, s/// changes only the first match in the current pattern space. Keep that default only when first-match behavior is deliberate.

Run the commands in this section to replace the first match on each line.

printf 'ERROR ERROR\nWARN ERROR\n' | sed 's/ERROR/INFO/'
INFO ERROR
WARN INFO

Replace every match on each line

Add g when the intent is a full line-wide replacement.

Run the commands in this section to replace every match on each line.

printf 'ERROR ERROR\nWARN ERROR\n' | sed 's/ERROR/INFO/g'
INFO INFO
WARN INFO

Switch delimiters for path-heavy patterns

Alternative delimiters keep path and URL substitutions readable.

Run the commands in this section to switch delimiters for path-heavy patterns.

printf '/var/log/app\n' | sed 's|/var/log|/srv/log|g'
/srv/log/app

Use capture groups with -E

-E removes most of the backslash noise from non-trivial patterns.

Run the commands in this section to use capture groups with -E.

printf '2026-04-14\n' | sed -E 's#([0-9]{4})-([0-9]{2})-([0-9]{2})#\3/\2/\1#'
14/04/2026

PowerShell

Replace every match with -replace

PowerShell’s -replace is regex-based and replaces all matches on each string.

Run the commands in this section to replace every match with -replace.

@('ERROR ERROR','WARN ERROR') | ForEach-Object { $_ -replace 'ERROR','INFO' }
INFO INFO
WARN INFO

Make the match case-sensitive with -creplace

Use -creplace when case-sensitive behavior matters.

Run the commands in this section to make the match case-sensitive with -creplace.

@('Error error','ERROR error') | ForEach-Object { $_ -creplace 'Error','WARN' }
WARN error
ERROR error

Reorder fields with capture groups

PowerShell uses $1, $2, and so on in the replacement string.

Run the commands in this section to reorder fields with capture groups.

@('name=alice','role=admin') | ForEach-Object { $_ -replace '^(.*)=(.*)$','$2=$1' }
alice=name
admin=role

These substitution flags are the ones you will use most often.

FlagMeaning
gReplace every match in the current pattern space
2, 3, …Replace only the Nth match on the line
pPrint the line if a substitution happened
w fileWrite changed lines to another file

Use this comparison when a pattern behaves differently than expected.

ConcernBRE defaultERE with -E
Grouping\(...\)(...)
AlternationNot portable; prefer -E instead of |`foo
One-or-moreNot portable; prefer -E instead of \++
Back-references in replacement\1, \2, …\1, \2, …

Addressing, line selection, and structural edits

Addresses restrict a command to the lines you care about. That keeps transformations precise and avoids awkward head or tail pipelines.

Bash/Linux

Suppress default output with -n when you want sed to act as a line extractor.

Run the commands in this section to print a line range with -n and p.

printf 'one\ntwo\nthree\nfour\n' | sed -n '2,3p'
two
three

Restrict a substitution to matching lines

An address in front of s/// limits the replacement to lines that match the address.

Run the commands in this section to restrict a substitution to matching lines.

printf 'INFO start\nERROR connect\nERROR retry\n' | sed '/ERROR/s/ERROR/WARN/'
INFO start
WARN connect
WARN retry

Delete blank lines

Deletion is one of sed’s most common cleanup tasks.

Run the commands in this section to delete blank lines.

printf 'alpha\n\nbeta\n' | sed '/^$/d'
alpha
beta

Replace matching lines with c\

Use c\ when the whole line should be replaced, not just one matched fragment.

Run the commands in this section to replace matching lines with c\.

printf 'INFO\nERROR\nDONE\n' | sed '/ERROR/c\WARN'
INFO
WARN
DONE

PowerShell

Select a line range

When the data is already in memory, array slicing is the closest equivalent to sed -n 'start,endp'.

Run the commands in this section to select a line range.

('one','two','three','four')[1..2]
two
three

Delete blank lines with Where-Object

Filtering with Where-Object covers the same case as sed '/^$/d'.

Run the commands in this section to delete blank lines with Where-Object.

@('alpha','','beta') | Where-Object { $_ -ne '' }
alpha
beta

Replace matching lines conditionally

Use a conditional pipeline when the whole line should change based on a match.

Run the commands in this section to replace matching lines conditionally.

@('INFO','ERROR','DONE') | ForEach-Object { if($_ -match '^ERROR$'){'WARN'} else {$_} }
INFO
WARN
DONE

These structural commands cover most line-oriented edits.

CommandMeaning
dDelete the current pattern space and start the next cycle
pPrint the current pattern space
i\Insert text before the current line
a\Append text after the current line
c\Replace the addressed line or range with new text
qQuit immediately

In-place editing and portability

By default, sed writes to stdout and leaves the source file untouched. That is the safest default. Reach for in-place editing only after a dry run, and prefer a backup suffix when the file matters.

Bash/Linux

Preview the change before writing

A dry run is the fastest way to confirm the substitution scope before you touch a file.

Run the commands in this section to preview the change before writing.

printf 'host=localhost\n' | sed 's/localhost/db.internal/'
host=db.internal

Edit a file with -i.bak

-i.bak keeps a backup and works across GNU sed and BSD or macOS sed.

Run the commands in this section to edit a file with -i.bak.

f=$(mktemp)
printf 'host=localhost\n' > "$f"
sed -i.bak 's/localhost/db.internal/' "$f"
printf 'file:%s\n' "$(cat "$f")"
printf 'backup:%s\n' "$(cat "$f.bak")"
rm -f "$f" "$f.bak"
file:host=db.internal
backup:host=localhost

PowerShell

Rewrite the file after transforming the content

PowerShell has no -i flag. The standard pattern is read, transform, write, and optionally create a backup first.

Run the commands in this section to rewrite the file after transforming the content.

$temp = Join-Path $env:TEMP ([System.IO.Path]::GetRandomFileName())
$backup = "$temp.bak"
Set-Content -LiteralPath $temp -Value 'host=localhost' -NoNewline
Copy-Item -LiteralPath $temp -Destination $backup
(Get-Content -LiteralPath $temp) -replace 'localhost','db.internal' | Set-Content -LiteralPath $temp -NoNewline
"file:$(Get-Content -LiteralPath $temp -Raw)"
"backup:$(Get-Content -LiteralPath $backup -Raw)"
Remove-Item -LiteralPath $temp,$backup
file:host=db.internal
backup:host=localhost

Keep these portability differences in mind before you automate.

FeatureGNU sedBSD or macOS sed
In-place edit with no backupsed -i 's/a/b/' filesed -i '' 's/a/b/' file
In-place edit with backupsed -i.bak 's/a/b/' filesed -i.bak 's/a/b/' file
Extended regex-E-E
Step addresses1~2Not available
Null-delimited mode-zNot available
Follow symlink target during -i--follow-symlinksNot available

GNU sed implements -i by writing a temporary file and renaming it over the original path. That is why -i can break symlink behavior by default and why --follow-symlinks matters on GNU sed. If link identity matters, stage the output explicitly instead of assuming -i preserves path semantics.

Do not combine -n and -i unless the script also prints explicitly. sed -ni 's/foo/bar/' file can truncate the file because output is suppressed and nothing writes the transformed lines back.

Multi-command and multiline workflows

Once a transformation needs more than one step, combine expressions with -e or use multiline commands such as N. If the script starts to look like a state machine, switch to awk or Python instead of forcing sed to carry the whole job.

Bash/Linux

Chain cleanup steps with multiple -e expressions

Multiple expressions are often enough for text normalization tasks.

Run the commands in this section to chain cleanup steps with multiple -e expressions.

printf '  alpha  \n\nbeta  \n' | sed -e 's/[[:space:]]*$//' -e '/^$/d'
  alpha
beta

Join line pairs with N

N appends the next input line to the current pattern space, which lets one substitution see both lines at once.

Run the commands in this section to join line pairs with N.

printf 'alpha\nbeta\ngamma\ndelta\n' | sed 'N;s/\n/ | /'
alpha | beta
gamma | delta

PowerShell

Chain trims and filters in one pipeline

The pipeline below mirrors the two-step cleanup from the Bash example.

Run the commands in this section to chain trims and filters in one pipeline.

@('  alpha  ','','beta  ') | ForEach-Object { $_ -replace '\s+$','' } | Where-Object { $_ -ne '' }
  alpha
beta

Collapse a wrapped value with a multiline regex

Use [regex]::Replace() when the input should be treated as one multiline string instead of a line stream.

Run the commands in this section to collapse a wrapped value with a multiline regex.

$text = "Subject: Hello`n World"
[regex]::Replace($text,"`n ",' ')
Subject: Hello World

Join records into one value

When the goal is a single combined value, array joining is usually simpler than emulating hold-space logic.

Run the commands in this section to join records into one value.

@('east','west','south') -join ', '
east, west, south

These commands are the ones to remember for multiline sed work.

CommandMeaning
NAppend the next input line to pattern space
PPrint pattern space only up to the first newline
DDelete up to the first newline and restart the cycle
h / HCopy or append pattern space into hold space
g / GCopy or append hold space into pattern space
xSwap pattern space and hold space

Data engineering patterns

sed is common in log cleanup, export normalization, and one-off redaction. The examples below stay in that operational zone rather than turning sed into a general parser.

Bash/Linux

Redact email-like tokens

For local inspection or quick sanitization, a regex replacement is often enough.

Run the commands in this section to redact email-like tokens.

printf 'user=ana@example.com\n' | sed -E 's/[[:alnum:]._%+-]+@[[:alnum:].-]+\.[[:alpha:]]{2,}/EMAIL_REDACTED/g'
user=EMAIL_REDACTED

Normalize a BOM, CRLF endings, trailing space, and blank lines

Chaining cleanup expressions keeps a small normalization pass readable.

Run the commands in this section to normalize a BOM, CRLF endings, trailing space, and blank lines.

printf '\xEF\xBB\xBFid,name\r\n1,Ada \r\n\r\n' | sed -e '1s/^\xEF\xBB\xBF//' -e 's/\r$//' -e 's/[[:space:]]*$//' -e '/^$/d'
id,name
1,Ada

PowerShell

Redact bearer tokens

For Windows-native pipelines, chained -replace operations cover the same kind of targeted redaction.

Run the commands in this section to redact bearer tokens.

@('Authorization: Bearer abc123.def') | ForEach-Object { $_ -replace 'Bearer [A-Za-z0-9._-]+','Bearer TOKEN_REDACTED' }
Authorization: Bearer TOKEN_REDACTED

Regex redaction is still only a local cleanup technique. If the job is compliance-sensitive or the patterns are diverse, use a real data-loss-prevention workflow instead of betting everything on one regex.

sed and PowerShell mapping

This table maps the most common sed patterns to the nearest PowerShell equivalent.

sed patternPowerShell equivalentNotes
sed 's/old/new/g' file(Get-Content file) -replace 'old','new'PowerShell replaces all matches by default
sed -n '5,10p' file(Get-Content file)[4..9]Array indexes are zero-based
sed '/pattern/d' file`(Get-Content file)Where-Object { $_ -notmatch ‘pattern’ }`
sed -n '/pattern/p' file`Select-String -Pattern ‘pattern’ -Path fileSelect-Object -ExpandProperty Line`
sed '/pattern/c\text' file`(Get-Content file)ForEach-Object { if(_ -match 'pattern'){'text'} else {_} }`
sed -i.bak 's/old/new/g' file`Copy-Item file file.bak; (Get-Content file) -replace ‘old’,‘new’Set-Content file`
`sed -E ‘s/(ab)/c/’ file``(Get-Content file) -replace ‘a
sed 's/[[:space:]]*$//' file(Get-Content file) -replace '\s+$',''Trim trailing whitespace

POSIX character classes

POSIX character classes are the safest way to keep sed regex portable across GNU sed and BSD or macOS sed.

ClassMeaningExample
[[:alpha:]]Letterssed 's/[[:alpha:]]//g'
[[:digit:]]Digitssed 's/[[:digit:]]//g'
[[:alnum:]]Letters and digitssed 's/[[:alnum:]]//g'
[[:space:]]Whitespace, including tabssed 's/[[:space:]]*$//'
[[:blank:]]Space and tab onlysed -E 's/[[:blank:]]+/ /g'
[[:upper:]] / [[:lower:]]Uppercase or lowercase letterssed 's/[[:upper:]]/X/g'

Choose sed, awk, or Python

  • Use sed when the unit of work is a line, a line range, or a regex-targeted rewrite.
  • Use awk when fields, numeric aggregation, or formatted reports matter more than raw substitution.
  • Use Python or Perl when the data has quoted CSV, nested structure, multiline records, or external integrations.
  • If the sed script needs several hold-space operations, branching, and special-case state, the extra language is usually the cleaner choice.

Troubleshooting

These failure modes are common because sed defaults are terse and easy to forget after a few weeks away from the tool.

Replacement scope

Only the first match changed

The default s/// behavior is first-match only. Add g when the line should be rewritten everywhere.

Run the commands in this section to only the first match changed.

printf 'foo foo\n' | sed 's/foo/bar/'
bar foo

Run the commands in this section to only the first match changed.

printf 'foo foo\n' | sed 's/foo/bar/g'
bar bar

Printing behavior

sed '2p' printed the selected line twice

Without -n, sed still performs its normal end-of-cycle print. Add -n when p is meant to be selective output rather than an extra print.

Run the commands in this section to sed '2p' printed the selected line twice.

printf 'one\ntwo\nthree\n' | sed '2p'
one
two
two
three

Run the commands in this section to sed '2p' printed the selected line twice.

printf 'one\ntwo\nthree\n' | sed -n '2p'
two

Regex dialect

+ or | matched literally

Portable sed does not treat alternation or one-or-more as BRE features. Use -E when that syntax is part of the pattern.

Run the commands in this section to + or | matched literally.

printf 'foo\nbar\n' | sed 's/foo|bar/baz/'
foo
bar

Run the commands in this section to + or | matched literally.

printf 'foo\nbar\n' | sed -E 's/foo|bar/baz/'
baz
baz