The s command is SED's workhorse — it handles nearly every use case you'll encounter in production. But most people only know the basic form. Mastering back-references, the & placeholder, alternate delimiters, and substitution flags turns SED from a simple find-replace tool into a powerful text transformation engine.
1
The s command — full syntax and all flags
BASH
# s/PATTERN/REPLACEMENT/FLAGS
# PATTERN: basic regex (or extended with -E)
# REPLACEMENT: text, &, \1..\9 back-references
# FLAGS: g, p, N (number), i/I, e, w file
# ── Flags ────────────────────────────────────────────────
sed 's/old/new/' # replace first match per line
sed 's/old/new/g' # replace ALL matches (global)
sed 's/old/new/2' # replace 2nd match only
sed 's/old/new/3g' # replace from 3rd match onwards (GNU)
sed 's/old/new/p' # print line if substitution occurred
sed -n 's/old/new/p' # print ONLY lines where substitution happened
sed 's/old/new/I' # case-insensitive match (GNU)
sed 's/old/new/gI' # case-insensitive + global
sed 's/old/new/w out.txt' # write changed lines to file
sed 's/command/&/e' # execute result as shell command (GNU)
# ── Alternate delimiters ──────────────────────────────────
# Use | # @ , to avoid escaping / in paths and URLs
sed 's|/old/path|/new/path|g'
sed 's#http://old#https://new#g'
sed 's@pattern@replacement@g'
sed 's,old,new,g'
2
& — the matched-text placeholder
BASH
# & in the replacement stands for the entire matched text
# ── Wrap matches ─────────────────────────────────────────
echo "price: 42.50" | sed 's/[0-9.]\+/[$&]/g'
# → price: [$42.50]
echo "ERROR: disk full" | sed 's/ERROR/[&]/g'
# → [ERROR]: disk full
# ── Quote specific tokens ─────────────────────────────────
sed 's/[A-Z_]\+/"\0"/g' # quote uppercase_CONSTANTS
sed 's/[0-9]\+/"&"/g' # quote all numbers
# ── Add prefix/suffix to matched text ─────────────────────
sed 's/^/ /' # indent every line 2 spaces
sed 's/$/ ;/' # append semicolon to every line
sed 's/^ERROR.*/ALERT: &/' # prepend ALERT: to ERROR lines
sed 's/\(host=\)\(.*\)/\1[&]/' # wrap value in brackets
# ── Build structured output ───────────────────────────────
sed 's/.*/{"line": "&"}/' # wrap each line as JSON string
sed 's/\(.*\)/\t\1/' # indent with tab
3
Back-references — capturing groups with \( \) and \1..\9
BASH
# Basic regex: groups with \( \), back-refs with \1..\9
# Extended regex (-E): groups with ( ), back-refs with \1..\9
# ── Reorder date fields DD/MM/YYYY → YYYY-MM-DD ──────────
echo "01/05/2026" | sed 's|\([0-9]\{2\}\)/\([0-9]\{2\}\)/\([0-9]\{4\}\)|\3-\2-\1|'
# → 2026-05-01
# Cleaner with -E (extended regex):
echo "01/05/2026" | sed -E 's|([0-9]{2})/([0-9]{2})/([0-9]{4})|\3-\2-\1|'
# ── Swap first two words on a line ────────────────────────
echo "first second rest" | sed -E 's/^(\S+) (\S+)/\2 \1/'
# → second first rest
# ── Capitalise first letter of each word ─────────────────
echo "hello world" | sed -E 's/\b([a-z])/\u\1/g' # GNU sed \u
# → Hello World
# ── Extract and restructure log fields ────────────────────
# Input: [2026-05-01 10:14:02] ERROR myapp: disk full
# Output: 2026-05-01 | ERROR | disk full
sed -E 's/\[([0-9-]+ [0-9:]+)\] ([A-Z]+) [^:]+: (.*)/\1 | \2 | \3/' app.log
# ── Redact sensitive fields ───────────────────────────────
sed -E 's/(password=)[^ &]*/\1***REDACTED***/gI' access.log
sed -E 's/([0-9]{1,3}\.){3}([0-9]{1,3})/x.x.x.\2/g' # mask IPs
sed -E 's/[0-9]{4}-[0-9]{4}-[0-9]{4}-([0-9]{4})/****-****-****-\1/g' # mask CC
4
Real-world substitution patterns
BASH
# ── HTTP to HTTPS upgrade across all configs ──────────────
find /etc/nginx -name "*.conf" \
-exec sed -i 's|http://internal\.|https://internal.|g' {} +
# ── Version bump in script files ──────────────────────────
sed -i -E 's/(VERSION=")[0-9]+\.[0-9]+\.[0-9]+(")/\12.1.0\2/' bin/*
# ── Environment variable templating ───────────────────────
# Replace {{VAR}} placeholders with environment values
sed -E "s|{{DB_HOST}}|${DB_HOST}|g;
s|{{DB_PORT}}|${DB_PORT}|g;
s|{{APP_ENV}}|${APP_ENV}|g" template.conf > output.conf
# ── Remove ANSI colour codes from log files ───────────────
sed -E 's/\x1B\[[0-9;]*[mK]//g' coloured.log > plain.log
# ── Normalise Windows line endings ────────────────────────
sed -i 's/\r//' file.txt # remove CR from CRLF
sed -i 's/$/\r/' file.txt # add CR (make CRLF)
# ── Trim trailing whitespace ──────────────────────────────
sed -i 's/[[:space:]]*$//' file.txt
# ── Double-space a file ───────────────────────────────────
sed 'G' file.txt # G appends newline + hold space
# ── Number non-blank lines ────────────────────────────────
sed '/./=' file.txt | sed '/./N;s/\n/\t/'
vriddh@prod-01:~/scripts$echo "date: 01/05/2026" | sed -E 's|([0-9]{2})/([0-9]{2})/([0-9]{4})|\3-\2-\1|'
date: 2026-05-01
vriddh@prod-01:~/scripts$echo "password=s3cr3t&token=abc123" | sed -E 's/(password=)[^ &]*/\1***REDACTED***/gI'
password=***REDACTED***&token=abc123
vriddh@prod-01:~/scripts$sed -E "s|{{DB_HOST}}|prod-db-01|g; s|{{APP_ENV}}|production|g" template.conf
DB_HOST=prod-db-01
APP_ENV=production
█
Basic vs Extended regex — In basic regex (
sed without -E), groups are \(...\) and quantifiers like + and ? need backslashes: \+ and \?. With -E (extended regex), use unescaped (), +, ?, and |. Always prefer -E for readability.✔ Substitution mastery — Use
& to reference the whole match in the replacement. Use \1–\9 with -E extended groups for capture. Use | as delimiter when the pattern contains / paths or URLs. Always use -E for complex patterns — basic regex backslash escaping is a trap. Test with sed -n 's/.../&/p' before using -i.