Shell Scripting SED Substitution Advanced May 2026

Shell Scripting Advanced SED: Substitution Deep Dive

Master the full s command — back-references with \1..\9, the & matched-text placeholder, alternate delimiters, flags (g, p, I, N, e), extended regex groups, and real-world substitution patterns for config editing, URL rewriting, and log transformation.

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.

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'
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
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
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/'
sed — substitution deep dive demo
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.