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.

CommandJumpsWhen
:label—declares a target, not a command
b labelto labelalways
bto the end of the scriptalways
t labelto labelif an s/// succeeded since the last line was read or the last t
tto the end of the scriptsame condition
T labelto labelif 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.

FormPrints the lineExit codePortable?
qyes0POSIX
qNyesNGNU only
Qno0GNU only
QNnoNGNU 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.