sed — Stream Editor#
What it is#
sed (stream editor) is a POSIX-standard utility included on every Unix and Linux system for performing non-interactive text transformations on a stream or file. It processes input line by line, applying a script of commands — most commonly substitution (s/pattern/replacement/) — and writes the result to stdout, optionally editing files in place with -i. Reach for sed for quick find-and-replace, line deletion, or insertion in scripts and pipelines; for anything involving multiple fields or conditional logic, awk is more readable.
Syntax#
A sed script is one or more [address]command pairs; the address selects which lines the command applies to and can be omitted to match every line. Multiple commands can be chained with -e or written in a file passed with -f.
sed [OPTIONS] 'SCRIPT' [FILE...]
sed [OPTIONS] -e 'CMD' -e 'CMD' [FILE...]
sed [OPTIONS] -f script.sed [FILE...]
Output: (none — exits 0 on success)
Common options#
| Option | Meaning |
|---|---|
-n | Suppress auto-print (only explicit p prints) |
-e | Add an expression |
-f | Read script from file |
-i | In-place edit (GNU: -i'' or -i suffix) |
-E / -r | Extended regex (no \+, | escaping) |
-z | NUL-delimited input (for multi-line via NUL) |
What’s new in GNU sed 4.10#
GNU sed 4.10 (released 20 April 2026) is the first stable release since 4.9 (Nov 2022) and rolls up 3.5 years of fixes. The headline items are large-file correctness, a TOCTOU fix for --follow-symlinks -i, and tighter POSIX-mode diagnostics. Nothing in the script language changed — every existing s///, address, and branch label keeps working identically.
| Change | Impact |
|---|---|
| Lines > 2 GiB | Global substitutions no longer panic past the 2³¹ byte offset |
--follow-symlinks -i | TOCTOU race fixed — an attacker can no longer redirect the symlink between resolve and open |
Backreferences + $ anchor | False matches on optional groups followed by end-of-line corrected |
\\c\[ in POSIX mode | No longer rejected; backslash-escape after a character class now handled |
--debug | No longer crashes when a label is compiled before option processing |
BRE a\*\* | Accepted as a GNU extension, matching grep’s behaviour |
| Diagnostics | Switched from grave-accent quoting to apostrophes (`x’ → ‘x’) |
| POSIX-mode warning | Non-portable backslash usage in s/// now produces a warning |
| Build | Compiles on platforms without <getopt.h> |
sed --version | head -1 # confirm 4.10 on Linux
# GNU sed 4.10 — macOS still ships BSD sed; install via: brew install gnu-sed
Output:
sed (GNU sed) 4.10
Address forms#
Addresses select which lines a command applies to.
| Address | Meaning |
|---|---|
N | Line N |
$ | Last line |
N,M | Lines N through M |
N~S | Every S-th line starting from N |
/regex/ | Lines matching regex |
/re1/,/re2/ | From re1-match through re2-match |
! suffix | Negate the address |
0,/re/ | GNU: from line 0 (first match even on line 1) |
sed '5d' file # delete line 5
sed '2,4d' file # delete lines 2–4
sed '$d' file # delete last line
sed '/^#/d' file # delete comment lines
sed '1~2d' file # delete odd lines (1,3,5,…)
sed '/START/,/END/d' file # delete range (inclusive)
Output: (none — exits 0 on success)
Given a file with lines 1–6 plus comment lines and a START/END block:
Output:
line1
line2
line3
line4
line6
Substitution — s#
The s command is the workhorse of sed: it replaces text matching a regex with a replacement string on each addressed line. Without the g flag only the first match on a line is replaced; use & in the replacement to refer to the entire match or \1…\9 for capture groups.
s/REGEX/REPLACEMENT/FLAGS
Output: (none — exits 0 on success)
| Flag | Meaning |
|---|---|
g | Replace all occurrences on the line |
N | Replace only the N-th occurrence |
i | Case-insensitive (GNU) |
p | Print line if substitution made |
w file | Write line to file if substitution made |
sed 's/foo/bar/' file # first occurrence per line
sed 's/foo/bar/g' file # all occurrences
sed 's/foo/bar/2' file # second occurrence only
sed 's/foo/bar/ig' file # case-insensitive, all
sed -n 's/error/ERROR/p' file # print only changed lines
Output:
bar baz foo
bar baz bar
foo baz bar
bar baz bar
2026-01-15 10:23:45 ERROR connection refused
Regex in substitutions#
# Capture groups: \1 \2 ... (BRE) or \1 with -E
sed 's/\(first\) \(last\)/\2 \1/' file # swap words (BRE)
sed -E 's/(first) (last)/\2 \1/' file # same, ERE
sed -E 's/([0-9]+)/[\1]/g' file # wrap numbers in []
sed 's/.*/ &/' file # indent every line
# & represents the entire match
sed 's/[A-Z][a-z]*/[&]/g' file # wrap each capitalized word
# Alternate delimiters (useful for paths)
sed 's|/usr/local|/opt|g' file
sed 's,/home/alice,/home/carol,g' file
Output:
last first
last first
order [42] placed on [2026]-[01]-[15]
Hello World
[The] [quick] [brown] [fox]
/opt/bin/tool
/home/carol/documents
Delete — d#
d removes the addressed lines from the output and immediately moves to the next cycle — no further commands in the script run for the deleted line. Combine it with an address (line number, regex, or range) to strip headers, blank lines, or comment blocks.
sed '/^$/d' file # delete empty lines
sed '/^\s*$/d' file # delete blank/whitespace-only lines
sed '/^#/d' file # delete comment lines
sed '1d' file # delete first line (skip header)
Output:
Hello World
foo bar
The quick brown fox
port=8080
Print — p#
p prints the current pattern space explicitly. It is almost always used with -n (which suppresses the default auto-print), so only lines selected by the address are printed — effectively making sed behave like a line-range or regex filter similar to grep.
sed -n '5,10p' file # print lines 5–10 (like head/tail)
sed -n '/ERROR/p' file # print matching lines (like grep)
sed -n '$p' file # print last line
sed -n '1p' file # print first line
Output:
2026-01-15 10:23:45 ERROR connection refused
2026-01-15 10:45:01 ERROR timeout waiting for response
Insert, append, change — i, a, c#
i inserts text before the addressed line, a appends text after it, and c replaces the entire line with new text. All three are useful for injecting config directives, banners, or boilerplate without a full substitution.
sed '1i\# Header comment' file # insert before line 1
sed '$a\# Footer' file # append after last line
sed '/^Host /a\ StrictHostKeyChecking no' ssh_config # append after match
sed '5c\REPLACED LINE' file # change (replace) line 5
sed '/old text/c\new text' file # change matching line
Output:
# Header comment
Hello World
foo bar
The quick brown fox
# Footer
Read/write files — r, w#
r splices the contents of a file into the output stream after the addressed line — handy for templating. w writes matching lines to a file instead of (or in addition to) stdout, letting you split a file into multiple outputs in a single pass.
sed '/INSERT_HERE/r extra.txt' template # splice file contents
sed -n '/ERROR/w errors.txt' app.log # write matches to file
Output: (none — exits 0 on success)
Quit — q, Q#
q prints the current line and exits immediately; Q exits without printing it. Both are more efficient than reading the whole file when you only need the beginning — sed '10q' is a lightweight substitute for head -10.
sed '10q' file # print first 10 lines then quit (like head -10)
sed '/DONE/q' file # quit after first matching line
sed '0,/START/d; /END/q' file # print lines between START and END
Output:
line1
line2
line3
line4
line5
line6
line7
line8
line9
line10
In-place editing#
-i rewrites the file on disk rather than printing to stdout — convenient but irreversible without a backup. Pass a suffix (e.g. .bak) to keep the original. BSD/macOS sed requires an explicit suffix even when you want no backup (-i ''), while GNU sed treats a bare -i as no backup.
# GNU sed
sed -i 's/foo/bar/g' file.txt # in-place, no backup
sed -i.bak 's/foo/bar/g' file.txt # in-place with .bak backup
# macOS (BSD sed requires explicit suffix even for no-backup)
sed -i '' 's/foo/bar/g' file.txt
# Multiple files
sed -i 's/oldhost/newhost/g' *.conf
find . -name "*.py" -exec sed -i 's/python2/python3/g' {} +
Output: (none — exits 0 on success)
Multiple expressions#
sed -e 's/foo/bar/g' -e '/^#/d' -e 's/baz/qux/' file
# Or a semicolon-separated script
sed 's/foo/bar/g; /^#/d; s/baz/qux/' file
Output:
bar qux
Hello World
The quick brown bar
Hold space (advanced)#
The hold space is a secondary buffer. Commands: h (copy pattern→hold), H (append), g (copy hold→pattern), G (append), x (exchange).
# Reverse line order of a file
sed -n '1!G; h; $p' file
# Print the line before a match
sed -n '/error/{x;p;d}; h' file
# Delete duplicate consecutive lines (like uniq)
sed '$!N; /^\(.*\)\n\1$/!P; D' file
Output:
line3
line2
line1
Practical recipes#
# Remove trailing whitespace
sed 's/[[:space:]]*$//' file
# Trim leading whitespace
sed 's/^[[:space:]]*//' file
# Remove blank lines
sed '/^[[:space:]]*$/d' file
# Convert Windows CRLF to LF
sed 's/\r$//' file
# Extract lines between two patterns (exclusive)
sed -n '/BEGIN/{n; /END/!{p; b}; b}; /BEGIN/,/END/{/BEGIN/d; /END/d; p}' file
# Simpler exclusive range
sed -n '/START/,/END/{/START/d;/END/d;p}' file
# Number non-empty lines
sed '/./=' file | sed 'N; s/\n/ /'
# Double-space a file
sed 'G' file
# Extract value from key=value config
sed -n 's/^database_host=//p' config.ini
# Comment out lines matching a pattern
sed '/^ServerName/s/^/# /' httpd.conf
# Uncomment lines matching a pattern
sed '/^# ServerName/s/^# //' httpd.conf
Output:
Hello World
foo bar
The quick brown fox
1 Hello World
2 foo bar
3 The quick brown fox
Hello World
foo bar
The quick brown fox
db.internal.example.com
# ServerName www.example.com
ServerName www.example.com
Complete command reference#
sed has more than a dozen single-letter commands beyond s/d/p. Most operate on the pattern space (the current line being processed) and a few interact with the hold space (a secondary buffer). Knowing the full alphabet is the difference between writing one-off substitutions and crafting maintainable sed scripts.
| Cmd | Description |
|---|---|
s | Substitute |
d | Delete pattern space, start next cycle |
D | Delete up to first newline in pattern space, restart cycle |
p | Print pattern space |
P | Print up to first newline in pattern space |
n | Replace pattern space with next input line |
N | Append next input line to pattern space (with \n) |
g | Replace pattern space with hold space |
G | Append hold space to pattern space (with \n) |
h | Replace hold space with pattern space |
H | Append pattern space to hold space (with \n) |
x | Exchange pattern and hold spaces |
i\ | Insert text before line |
a\ | Append text after line |
c\ | Change (replace) line(s) |
r FILE | Read FILE, queue for output after this line |
w FILE | Write pattern space to FILE |
R FILE | GNU: read one line from FILE per cycle |
W FILE | GNU: write up to first newline of pattern space to FILE |
= | Print line number |
l | List pattern space showing non-printable chars |
q | Quit (printing current line) |
Q | Quit without printing |
b LABEL | Unconditional branch to label |
t LABEL | Branch to label IF a substitution succeeded since last input |
T LABEL | GNU: branch IF NO substitution succeeded since last input |
: LABEL | Define a branch target |
y/src/dst/ | Transliterate (one-for-one char map, like tr) |
z | GNU: zap (empty) pattern space |
F | GNU: print current filename |
e [CMD] | GNU: execute CMD (or pattern space) as shell command |
Substitution flags in depth#
The s command takes optional flags after the closing delimiter. They are independent and can be combined (gip, 2g, etc.). The numeric flag is special: it replaces only the N-th match on the line — combine with g to replace the N-th match and everything after it.
| Flag | Meaning |
|---|---|
g | Replace all matches on the line |
N (1–9) | Replace only the N-th match |
Ng | Replace from N-th match onwards |
i / I | Case-insensitive (GNU) |
p | Print the line if a substitution was made |
w file | Write the line to file if a substitution was made |
e | GNU: execute the result as a shell command and replace |
m / M | GNU: multi-line mode (when pattern space has \n) |
# Replace ONLY the 3rd occurrence
echo "a a a a a" | sed 's/a/X/3'
# Replace the 3rd and every later occurrence
echo "a a a a a" | sed 's/a/X/3g'
# Case-insensitive global replace, print only changed lines
sed -n 's/error/ERROR/Igp' app.log
# Substitute and write the changed lines to a separate file
sed -n 's/^FAIL/PASS/wp.log' results.txt
# `e` flag: substitute into a command, then execute it
echo "ls" | sed 's/.*/& -la/e'
Output (echo "a a a a a" | sed 's/a/X/3'):
a a X a a
Output (echo "a a a a a" | sed 's/a/X/3g'):
a a X X X
Backreferences and &#
In a replacement string, & stands for the entire match and \1–\9 reference the first nine capture groups (parenthesized subexpressions). Backreferences also work inside the regex itself to match a previously captured group — this is how you find repeated tokens. To get a literal & or \, escape it as \& or \\.
# Wrap every IPv4 address in brackets
sed -E 's/([0-9]+\.[0-9]+\.[0-9]+\.[0-9]+)/[\1]/g' access.log
# Swap two columns separated by colons
sed -E 's/^([^:]+):([^:]+)$/\2:\1/' pairs.txt
# Quote every word starting with capital
sed -E 's/\b([A-Z][a-z]+)\b/"\1"/g' file
# Use & to repeat the match (here: duplicate every digit)
echo "abc 12 xyz 9" | sed -E 's/[0-9]+/& &/g'
# Backreference inside the pattern (find repeated words)
sed -nE 's/\b(\w+) \1\b/MATCH: &/p' essay.txt
# Literal & and \ in replacement
echo "AT" | sed 's/AT/Tom \& Jerry/' # & is reserved
echo "AT" | sed 's/AT/back\\slash/' # \\ -> \
Output (echo "abc 12 xyz 9" | sed -E 's/[0-9]+/& &/g'):
abc 12 12 xyz 9 9
Addresses in depth#
Addresses select the lines a command applies to. They can be line numbers, regexes, ranges, or a special form. Suffixing ! negates the address. GNU sed adds the 0,/re/ form, which is critical when the first match might occur on line 1 (the standard 1,/re/ would never terminate the range in that case).
# Line-number addresses
sed '3,7d' file # delete lines 3 through 7
sed '$!d' file # delete every line except the last
sed '5,$d' file # delete from line 5 to EOF
sed '0~3p' -n file # every 3rd line (0,3,6,9,…) — GNU
sed '1~2d' file # delete odd lines — GNU
# Regex addresses
sed '/^#/d' file # delete comment lines
sed '/^$/,/^$/d' file # delete every block ending in blank
sed '/^DEBUG/!d' file # KEEP only DEBUG lines (delete others)
# Range from regex to regex
sed -n '/BEGIN/,/END/p' file # inclusive
sed '/BEGIN/,/END/d' file # delete inclusive range
# GNU 0,/re/ form — handles match on line 1
sed '0,/foo/{s/foo/bar/}' file # replace only first occurrence (anywhere)
sed '1,/foo/{s/foo/bar/}' file # WRONG if foo is on line 1
Output (sed -n '/BEGIN/,/END/p' file):
BEGIN
intro
body
END
GNU sed vs BSD/macOS sed#
The two implementations diverge in three places that bite people daily: in-place editing (-i), extended-regex flag (-E vs -r), and the GNU-only i\/a\/c\ shortcuts. macOS ships BSD sed by default; brew install gnu-sed provides gsed as a workaround. The cleanest cross-platform strategy is to avoid -i entirely in shared scripts.
| Behaviour | GNU sed | BSD/macOS sed |
|---|---|---|
| In-place no backup | sed -i or sed -i '' | sed -i '' (suffix required) |
| In-place with backup | sed -i.bak | sed -i.bak |
| Extended regex | -E or -r | -E only |
i\TEXT on one line | works | requires literal newline |
0,/re/ address | supported | not supported (use /re/ workaround) |
\b word boundary | supported | not in BRE |
Case-insensitive s///i | supported | supported |
T (negated t) | supported | not supported |
# Portable in-place pattern (no -i)
tmp=$(mktemp) && sed 's/foo/bar/g' file > "$tmp" && mv "$tmp" file
# Portable extended regex flag
sed -E 's/(foo|bar)/X/g' file # both implementations
# Portable insert: feed sed a real newline
sed '1i\
# Header line
' file
# Portable replace-first-match (avoids 0,/re/ on BSD)
sed -e ':a' -e '/foo/{s//bar/; bb}' -e 'n; ba' -e ':b' file
Output: (none — exits 0 on success)
Pattern space and hold space — the mental model#
sed processes input in cycles: read one line into the pattern space, run the entire script top to bottom, then (unless -n) print the pattern space and start the next cycle. The hold space is a separate buffer that survives between cycles, accessed only by h, H, g, G, and x. Almost every “magic” sed one-liner is hold-space gymnastics.
+-------------------+ +------------------+
| input line 1 | -----> | pattern space |
+-------------------+ read +------------------+
^ ^ ^ ^
| | | | commands run here
v v v v
+------------------+
| hold space | (h H g G x)
+------------------+
|
v auto-print (unless -n)
stdout
# Show every two consecutive lines together (line N and N+1)
sed 'N; s/\n/ | /' file
# Tac (reverse line order) using hold-space accumulation
sed -n '1!G; h; $p' file
# Print line N along with the preceding line (here N=3)
sed -n '2{h;n;d}; 3{x;p;x;p}' file
# Keep the LAST occurrence of a duplicate (opposite of `uniq`)
sed -n 'G; /^\([^\n]*\)\n.*\1.*/!P; h' file
Output: (none — exits 0 on success)
Branching with labels, b, t, T#
Labels (:name) act like targets for b (unconditional branch), t (branch if last s/// succeeded), and T (branch if it did not — GNU only). With branches, sed becomes Turing-complete; in practice they are essential for “keep substituting until no more changes” loops and multi-line aggregations.
# Squeeze runs of blank lines to a single blank line
sed -e '/./,/^$/!d' file
# Repeatedly substitute until no further match (multi-pass on one line)
sed ':loop
s/ / /g
t loop' file
# Join lines ending in backslash (line continuation)
sed -e ':start' -e '/\\$/{N; s/\\\n//; b start}' file
# Replace only OUTSIDE a region — skip lines between BEGIN…END
sed '/BEGIN/,/END/b skip
s/foo/bar/g
:skip' file
Output (sed ':loop; s/ / /g; t loop' "x y z"):
x y z
r, w, R, W — file I/O#
r FILE and w FILE are sed’s I/O primitives. r queues the contents of a file to be printed after the current pattern space; w writes pattern space to a file. GNU R and W are line-at-a-time variants useful for interleaving inputs.
# Append a footer file after every match of a marker
sed '/MARKER/r footer.txt' template > out
# Split a log into two files by severity
sed -n '/ERROR/w errors.log
/WARN/w warns.log' app.log
# Interleave two files line-by-line (zip)
sed 'R file2' file1
Output (sed '/MARKER/r footer.txt' template):
intro line
MARKER
--- footer line 1 ---
--- footer line 2 ---
body line
Delimiters in s///#
The / separator in s/pat/repl/ can be any single character that does not appear in the pattern or replacement. For paths, URLs, or anything containing slashes, switch to |, #, ,, or @ — this avoids tedious escaping like s/\/usr\/local\/bin/\/opt\/bin/. The delimiter must be the same character three times.
sed 's|/usr/local|/opt|g' paths.txt
sed 's#https://#http://#g' urls.txt
sed 's,/home/alice,/home/carol,g' paths.txt
sed -E 's@\.com/(api|auth)@.net/\1@g' endpoints.txt
Output: (none — exits 0 on success)
Transliteration with y///#
y/SRC/DST/ is sed’s mini-tr: every character in SRC maps to the corresponding character in DST, one-for-one. Useful for case conversion of a fixed alphabet or simple ciphers. Unlike s, y does not understand regex.
# Uppercase every letter on lines matching /^name:/
sed '/^name:/y/abcdefghijklmnopqrstuvwxyz/ABCDEFGHIJKLMNOPQRSTUVWXYZ/' file
# ROT-13
sed 'y/ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz/NOPQRSTUVWXYZABCDEFGHIJKLMnopqrstuvwxyzabcdefghijklm/' file
# Swap tabs for spaces (single-character)
sed 'y/\t/ /' file
Output: (none — exits 0 on success)
sed vs awk vs perl -pe#
These three tools occupy the same “stream editor” niche; choosing between them is mostly about regex needs and complexity. sed wins on size and portability, awk on field-oriented data, perl on regex power and one-off scripts.
| Need | Reach for |
|---|---|
| Simple line-by-line substitution | sed s/// |
| In-place edits across many files | sed -i or perl -i -pe |
Field-aware logic ($3 > 100) | awk |
| Aggregation, sums, counts | awk |
Modern regex (\d, lookarounds, non-greedy) | perl -pe |
| Multi-line patterns | perl -0pe or sed -z |
| JSON / structured data | jq (not sed) |
| CSV with quoted commas | awk -v FPAT=... or csvkit |
# Same task: replace foo with bar on every line, three ways
sed 's/foo/bar/g' file
awk '{gsub(/foo/, "bar"); print}' file
perl -pe 's/foo/bar/g' file
# Modern regex with lookbehind — sed cannot do this; perl can
perl -pe 's/(?<=Bearer )\S+/REDACTED/' headers.txt
# Slurp whole file as one record — perl idiom, hard in sed
perl -0pe 's/<form.*?<\/form>/<form>REDACTED<\/form>/gs' page.html
Output: (none — exits 0 on success)
Modern alternative — sd#
sd (current release v1.1.0, Feb 2026) is a Rust find-and-replace tool whose pitch is “sed with the s/// dialect everyone actually remembers”. Patterns use the same regex flavour as JavaScript / Python (\d, \w, named groups, non-greedy *?), and the replacement syntax is $1 / ${name} instead of \1. It is purpose-built for the one job that 90% of sed invocations actually do — global substitution — so it skips addressing, hold space, branching, and -n. For everything else, you still want sed.
# Install
cargo install sd # or: brew install sd
# Stdin pipe — no need for / delimiters
echo "hello world" | sd 'world' 'rust'
# File in place (atomic write — safe to interrupt, unlike sed -i)
sd 'old_api' 'new_api' src/**/*.py
# Preview before writing (no -i flag needed; -p shows the diff)
sd -p 'foo' 'bar' file.txt
# Literal/string mode — no escaping of regex metacharacters
sd -F 'a.b.c' 'x-y-z' config.ini
# Modern regex — lookarounds, non-greedy, \d, named groups
sd '(?<year>\d{4})-(?<mon>\d{2})' '${mon}/${year}' dates.txt
# Across-line matching (multi-line / DOTALL semantics)
sd -A '<form>.*?</form>' '<form>REDACTED</form>' page.html
Output (echo "hello world" | sd 'world' 'rust'):
hello rust
| Capability | sed | sd |
|---|---|---|
| POSIX BRE / ERE | Yes | No (Rust regex only) |
Lookarounds, \d, non-greedy | No | Yes |
| Address ranges, hold space, branching | Yes | No |
| Atomic in-place writes | No (-i is not atomic) | Yes |
| Preview without writing | No | Yes (-p) |
| Literal-string mode | No (must escape) | Yes (-F) |
| Speed on large regex jobs | Baseline | ~11× faster (reported) |
| Drop-in for existing sed scripts | Yes | No |
[!TIP] Reach for
sdfor interactive search-and-replace at the shell, especially when the pattern has slashes, escapes, or lookarounds. Keepsedfor scripts, pipelines you ship to other people, and anything that needs-i.bak, hold space, or address ranges. They coexist —sddoes not replace sed in CI or POSIX environments.
Pitfalls and gotchas#
- Greedy quantifiers:
.*is greedy.s/<.*>/X/on<a><b>becomesX, notX<b>. Use[^>]*for “anything up to the next>” or switch toperl -pefor non-greedy.*?. - Special chars in delimiter:
s/a/b/breaks ifacontains a literal/. Switch delimiter (s|a|b|) — much easier than escaping every slash. - Backslash in replacement:
&,\1–\9, and\\are special in the replacement string. To insert a literal\, write\\. - BSD vs GNU
-i: macOS sed treats-ias “use this suffix for backup”, sosed -i 's/x/y/' fileconsumess/x/y/as the backup suffix. Always pass''on BSD:sed -i '' 's/x/y/' file. - Newlines in replacement: BRE/ERE replacements cannot contain a literal newline directly. Use
\nonly with GNU sed; portable form is to end the line with\and continue on the next. - Shell quoting: single-quote sed scripts. Inside double quotes the shell eats
\,$, and backticks before sed sees them. dandDdiffer:ddeletes the entire pattern space and starts the next cycle.Ddeletes only up to the first embedded newline (relevant when you have usedNto append a second line).nandNdiffer: lowercasenreplaces pattern space with the next line. UppercaseNappends the next line with an embedded\n.-iis not transactional: a failing sed mid-stream leaves the file partially written. Use amktemp/mvpattern for anything irreplaceable.
Workflow recipes#
# 1. Batch-rename a key in every YAML file under a tree
find . -name '*.yaml' -print0 | xargs -0 sed -i 's/^oldKey:/newKey:/'
# 2. Extract the section between two markers (exclusive of markers)
sed -n '/^## Installation/,/^## /{/^## /d; p}' README.md
# 3. Extract the value of a key from a key=value config
sed -n 's/^[[:space:]]*database_host[[:space:]]*=[[:space:]]*//p' app.ini
# 4. Normalize line endings (CRLF → LF, and trailing whitespace away)
sed -i 's/\r$//; s/[[:space:]]*$//' file.txt
# 5. Indent every line by 4 spaces
sed 's/^/ /' file
# 6. Strip ANSI color escape codes from a log
sed -E 's/\x1B\[[0-9;]*[mK]//g' colored.log
# 7. Add a comment on the first occurrence of a directive
sed '0,/^Listen 80$/{s//# Default HTTP port\nListen 80/}' httpd.conf
# 8. Print every paragraph (blank-line-separated record) that mentions FOO
sed -n '/./{H; $!d}; x; /FOO/p' file
# 9. Replace text matching $OLD with $NEW, escaping shell variables safely
old='alice@example.com'
new='carol@example.com'
# Escape regex/replacement meta-characters for sed
esc_old=$(printf '%s' "$old" | sed -e 's/[]\/$*.^[]/\\&/g')
esc_new=$(printf '%s' "$new" | sed -e 's/[\/&]/\\&/g')
sed -i "s/${esc_old}/${esc_new}/g" addresses.txt
# 10. Find-then-edit with rg (preview first, then commit)
rg -l 'old_api' src/ | xargs sed -i 's|old_api|new_api|g'
Output (sed -n '/^## Installation/,/^## /{/^## /d; p}' README.md):
Install via your platform package manager:
apt install foo
brew install foo
After installation, verify with `foo --version`.
One-liners reference#
sed -n '$=' file # count lines (wc -l)
sed -n '5p' file # print line 5
sed -n '$p' file # print last line
sed -n '/re/p' file # grep equivalent
sed -n '/re/!p' file # grep -v equivalent
sed '/./,$!d' file # remove leading blank lines
sed -e :a -e '/^\n*$/{$d;N;ba' -e '}' file # remove trailing blank lines
sed = file | sed 'N;s/\n/\t/' # number every line (cat -n)
sed '$!N;s/\n/ /' file # join every pair of lines
sed 'G' file # double-space
sed -n 'p;N' file # double-space (alt)
sed 'n;d' file # print only odd lines
sed '1~2d' file # print only even lines (GNU)
sed '/^$/N;/\n$/D' file # squeeze multiple blanks to one
sed 's/.$//' file # delete last char of each line
sed 's/^.//' file # delete first char of each line
sed '1!G;h;$!d' file # reverse lines (tac)
Output: (none — exits 0 on success)
[!TIP] For complex, multi-field transformations,
awkis usually clearer thansed. Usesedfor line-oriented substitutions and deletions; switch toawkwhen you need to reference fields or do arithmetic.
[!TIP] When a sed script grows past a few commands, move it into a file (
script.sed) and runsed -f script.sed. You can add# commentsand break logic across lines, which makes branching scripts much easier to read.
[!TIP] Pair
rg -l(orgrep -rl) withsed -ifor a fast “find-then-replace” pipeline across a tree. Preview the file list first, then run the substitution.
[!WARN] The in-place behaviour differs between GNU sed (
-isuffix optional) and BSD/macOS sed (-i ''required). For portable scripts, write to a temp file andmvinstead of using-i.
[!WARN]
sed -iis not atomic — an interrupted run can leave a file truncated. For anything irreplaceable, use amktemp/mvpattern or work on a copy.