SED's branching commands make it Turing-complete. While most SED scripts are linear, labels and branches let you build loops, skip processing blocks conditionally, and implement if-else logic — all inside a single stream pass. Understanding branching transforms SED from a find-replace tool into a full text transformation engine.
1
Branch commands — b, t, T
BASH
# :label — define a branch target (no output, no state change)
# b label — unconditional jump to :label
# b — jump to end of script (print current pattern space, next cycle)
# t label — jump to :label IF any s/// succeeded since last line/t
# T label — jump to :label IF no s/// succeeded since last line/T (GNU)
# ── Skip to end (b without label) ────────────────────────
sed '/^#/b # skip rest of script for comment lines
s/old/new/g
s/foo/bar/g'
# ── Conditional skip: if-else ─────────────────────────────
sed '/ERROR/{
s/ERROR/CRITICAL/
b done # skip WARN processing
}
/WARN/{
s/WARN/WARNING/
}
:done'
# ── Loop with t — substitute until stable ────────────────
sed ':loop
s/ / / # collapse two spaces to one
t loop' # if s succeeded, loop; else stop
# Result: any run of spaces becomes a single space
# ── Loop with b — unconditional ───────────────────────────
sed ':top
/\\$/{ # if line ends with backslash
N # append next line
s/\\\n// # remove backslash + newline
b top # loop back to check for more
}'
# Joins all backslash-continued lines
# ── T — branch on substitution FAILURE (GNU sed) ──────────
sed 's/^[[:space:]]*/&/ # attempt trim (always succeeds trivially)
T skip # branch if no substitution: won'\''t fire here
s/ \+/ /g # normalise spaces
:skip'
2
Building loops — practical patterns
BASH
# ── Remove all occurrences of nested brackets ─────────────
sed ':loop
s/([^()]*)// # remove innermost () pair
t loop' # repeat until no more () remain
# Input: func(a(b(c))) → Output: func
# ── Strip all HTML tags ───────────────────────────────────
sed ':loop
s/<[^>]*>//g
t loop' # handles tags that span lines when combined with N
# ── Collapse all blank lines to one ──────────────────────
sed '/^$/{ N; /^\n$/d; P; D }'
# If blank line: read next; if still blank: delete; else P+D
# ── Convert tab-indented tree to dash-indented ────────────
sed ':loop
s/^\t/ - / # first tab becomes " - "
t loop' # loop: replace each leading tab
# ── Process lines until a sentinel ────────────────────────
sed '/^STOP$/{ b end }
s/old/new/g
b
:end
q' # stop processing at STOP line
# ── Print only changed lines ──────────────────────────────
sed -n 's/ERROR/FIXED/p' # p flag: print only if substitution happened
# ── Multi-step transform with branch guard ────────────────
sed -E '/^[[:space:]]*$/b # skip blank lines
/^#/b # skip comment lines
s/\bfoo\b/bar/g # normalise tokens
s/\bFOO\b/BAR/g
s/[[:space:]]+/ /g # normalise spaces
s/[[:space:]]*$// # strip trailing whitespace'
3
If-else logic in SED
BASH
# SED has no native if-else, but you can simulate it with
# address negation (!) and branching
# ── Pattern: if match → do A; else → do B ─────────────────
sed '/pattern/{
s/old/new/
b end
}
# else: (lines that did NOT match /pattern/)
s/other/thing/
:end'
# ── Three-way branch: ERROR, WARN, INFO ───────────────────
sed '/\bERROR\b/{
s/^/[CRITICAL] /
b done
}
/\bWARN\b/{
s/^/[WARNING] /
b done
}
/\bINFO\b/{
s/^/[INFO] /
}
:done' app.log
# ── Process only lines matching a count condition ─────────
# Apply substitution only to first 3 ERRORs found
sed '/ERROR/{
0~3!{ b } # branch (skip) unless 3rd, 6th, 9th... match
s/ERROR/SAMPLED_ERROR/
}'
# ── Simulate if line N matches, transform line N+1 ────────
sed '/^HEADER$/{
n # read next line into pattern space
s/old_value/new_value/
}'
4
Real-world branching workflows
BASH
# ── Idempotent config update ──────────────────────────────
# Only set DB_HOST if it doesn't already have the right value
sed -i '/^DB_HOST=prod-db-01$/b # already correct, skip
s/^DB_HOST=.*/DB_HOST=prod-db-01/' config.env
# ── Environment-specific substitution ────────────────────
ENV="production"
sed "
/^APP_ENV=/{
s/=.*/=${ENV}/
b done
}
/^LOG_LEVEL=/{
s/=.*/=$([ \"${ENV}\" = production ] && echo WARN || echo DEBUG)/
}
:done" config.env
# ── Remove C-style comments with loop ─────────────────────
sed ':loop
s|/\*[^*]*\*\+\([^/*][^*]*\*\+\)*/||g
t loop
/\/\*/{ N; b loop }'
# ── Complete SED script: clean a config file ─────────────
sed -E '
/^[[:space:]]*#/b # keep comment lines as-is
/^[[:space:]]*$/d # delete blank lines
:norm
s/[[:space:]]+/ /g # normalise internal spaces
s/^[[:space:]]+// # strip leading space
s/[[:space:]]+$// # strip trailing space
/= *$/{ # value-less keys
s/=$/= /
b norm
}' messy.conf
vriddh@prod-01:~/scripts$echo "a b c d" | sed ':l; s/ / /; tl'
a b c d
vriddh@prod-01:~/scripts$printf 'ERROR db down\nWARN slow query\nINFO startup\n' | sed '/\bERROR\b/{s/^/[CRITICAL] /; b done}; /\bWARN\b/{s/^/[WARNING] /; b done}; s/^/[INFO] /; :done'
[CRITICAL] ERROR db down
[WARNING] WARN slow query
[INFO] INFO startup
█
✔ Branching rules — Use
b without a label to skip to end of script (print and next cycle). Use t label to loop after successful substitution. Reset the t flag by starting a new cycle or explicitly with a no-op s/$/$/. Prefer address-based guards (/pattern/b) over complex branching when possible — they're more readable. When branching logic grows beyond 5-6 commands, switch to AWK.