cheat sheet
sed
Non-interactive text transformation. Substitution, deletion, insertion, address ranges, in-place editing, and multi-line patterns with practical recipes.
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 |
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)
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.
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.
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.