sed Cheatsheet
Branching and Labels
Use this sed reference while you build software engineering projects, review code, or refresh the syntax you reach for most.
The four commands
sed is a real programming language with exactly one control-flow primitive, the
jump. Labels are declared with :name and jumped to by b, t, or T.
| Command | Jumps | When |
|---|---|---|
:label | — | declares a target, not a command |
b label | to label | always |
b | to the end of the script | always |
t label | to label | if an s/// succeeded since the last line was read or the last t |
t | to the end of the script | same condition |
T label | to label | if no s/// has succeeded. GNU only |
# unconditional: skip the rest of the script for matching lines gsed '/^#/b; s/foo/bar/' file # GNU only, see the BSD trap below sed -e '/^#/b' -e 's/foo/bar/' file # both, comments pass through untouched # conditional: only decorate lines that actually changed gsed 's/foo/bar/;t end;b;:end;s/$/ [edited]/' file
The t flag is reset by reading a new line and by a successful t. It is not
reset by a failed s///, which is the subtle part when you chain several.
Loop until a substitution stops matching
:a plus ta is the sed equivalent of while. It is the only way to apply a
substitution repeatedly when g is not enough, because g cannot re-scan text it
already produced.
# group digits with commas, right to left sed -E -e :a -e 's/([0-9])([0-9]{3})($|,)/\1,\2\3/' -e ta # expand tabs to 4 spaces, aligning to a real tab stop sed -e :a -e 's/^\(.\{1,3\}\)\t/\1 /;ta' file # collapse runs of spaces, one pass at a time sed -e :a -e 's/ / /' -e ta file # join backslash-continued lines sed -e :a -e '/\\$/N; s/\\\n//; ta' file
Verify the comma example:
printf '1234567\n' | sed -E -e :a \ -e 's/([0-9])([0-9]{3})($|,)/\1,\2\3/' -e ta # 1,234,567
The BSD label trap
This is the one that ruins scripts written on Linux and run on a Mac. BSD sed
treats everything after b, t, or : up to the end of the script fragment as the
label name, semicolons included.
# GNU: works. BSD/macOS: "unused label 'a;s/x/y/;ta'" sed ':a;s/x/y/;ta' file # GNU: works. BSD/macOS: "undefined label ';s/$/ X/'" sed 's/a/X/;t;s/$/ X/' file
Two spellings survive both:
# one -e per command sed -e :a -e 's/x/y/' -e ta file sed -e 's/a/X/' -e t -e 's/$/ X/' file # real newlines in a single script sed ' :a s/x/y/ ta' file
- A label is at most 8 characters in strict POSIX. Keep them to one or two letters.
- No space is required after :, and BSD would swallow one into the name anyway, so
write :a not : a.
- Always put a label command last in its own -e. -e ':a;p' names the label
a;p on BSD.
T, the GNU-only inverse
T branches when no substitution has happened, which saves the t/b dance for
the common "annotate what did not match" case.
gsed 's/a/X/;T;s/$/ CHANGED/' file # tag only the lines that changed gsed 's/a/X/;T skip;s/$/ ok/;:skip' file
printf 'aaa\nbbb\n' | gsed 's/a/X/;T;s/$/ CHANGED/' # Xaa CHANGED # bbb
The portable equivalent inverts the sense with t and a jump past the tail:
sed -e 's/a/X/' -e 't yes' -e b -e ':yes' -e 's/$/ CHANGED/' file
q and Q, stopping early
q prints the pattern space, flushes any pending a text, and exits. Q exits
without printing anything. Both accept an exit code on GNU only.
| Form | Prints the line | Exit code | Portable? |
|---|---|---|---|
q | yes | 0 | POSIX |
qN | yes | N | GNU only |
Q | no | 0 | GNU only |
QN | no | N | GNU only |
sed 10q file # like head -10, portable sed -n '/pat/{p;q;}' file # first match only, stops reading gsed '3Q' file # print lines 1-2, portable form is 2q gsed '/fatal/q1' file # use sed as a test, exit 1 on a match sed '2q5' file # BSD: "extra characters at the end of q command"
q is a real performance tool. On a multi-gigabyte log, sed -n '/pat/{p;q;}' stops
at the first hit while grep -m1 is the tool most people reach for and sed -n
'/pat/p' reads everything.
A worked multi-command script
Once a script needs branching, put it in a file. Comments start with #, one
command per line, and none of the BSD label problems apply.
cat > fix.sed <<'EOF' # skip comments and blanks entirely /^[[:space:]]*#/b /^[[:space:]]*$/b # normalize key = value to key=value s/[[:space:]]*=[[:space:]]*/=/ # unwrap continuation lines :join /\\$/{ N s/\\\n[[:space:]]*// bjoin } # tag anything still suspicious /=$/s/$/ # EMPTY VALUE/ EOF sed -f fix.sed config.ini sed -n -f fix.sed config.ini # -n still applies
- -f and -e can be combined and are applied in the order given.
- A #n as the very first line of a script file is equivalent to -n.
- Blocks written with { and } on their own lines are the readable way to nest,
and they need no semicolons at all.