Most real sed jobs rearrange parts of a line and then save the result back into the file.
Parentheses in the pattern capture what they match; \1 to \9 in the replacement insert those captures. In BRE the parentheses must be escaped, so use sed -E (ERE) and write plain ( ):
sed -E 's/([a-z]+)@([a-z.]+)/\2: \1/'GNU sed also offers case conversion in the replacement: \U upper-cases what follows, \L lower-cases, \E stops; \u and \l affect only the next character.
To change a file in place use -i. GNU sed writes a temporary file and renames it over the original. Give a suffix to keep a backup: sed -i.bak 's/a/b/' file leaves file.bak behind.
Portability matters here. On macOS and the BSDs -i requires a suffix argument, so sed -i 's/a/b/' f fails there; the empty suffix is written sed -i '' .... The form -i.bak (no space) works on both GNU and BSD sed, so prefer it in shared scripts, then delete the backup if you don't need it.
Before running -i on many files, preview the change without -i (or with diff) first. Because -i replaces the file, it also breaks hard links and resets ownership in some cases, which matters for files edited as root.
Example
echo 'alice@example.com' | sed -E 's/([a-z]+)@([a-z.]+)/\2: \1/'
# example.com: alice
echo '2026-10-17' | sed -E 's/([0-9]{4})-([0-9]{2})-([0-9]{2})/\3.\2.\1/'
# 17.10.2026
echo 'hello world' | sed -E 's/(\w+)/\u\1/g' # Hello World (GNU)
printf 'debug=true\nlevel=3\n' > app.conf
sed -i.bak 's/^debug=true/debug=false/' app.conf # works with GNU and BSD sed
cat app.conf # debug=false / level=3
cat app.conf.bak # debug=true / level=3
diff app.conf.bak app.conf # shows the one changed lineRemember
- sed -E lets you write ( ) for groups; \1..\9 reuse them in the replacement
- -i edits in place; -i.bak keeps a backup and is portable to BSD/macOS
- Preview without -i before rewriting many files
Watch out for
- Running sed -i 's/x/y/' file on macOS, where the s/// expression is taken as the backup suffix and the command fails.