Why sed
When you need to rewrite lines in a stream (a log, a config file, command
output), sed is the fastest tool: one command, no temporary files, no
Python. It works line by line, without buffering the entire input into
memory. It fits naturally in pipelines and find -exec.
If the task goes beyond per-line substitution, reach for [[cmd-awk|awk]] or Python.
Basic syntax
sed [OPTIONS] 'SCRIPT' [FILE...]
sed [OPTIONS] -f script.sed [FILE...]
Without a file argument, sed reads stdin. The script is a set of commands
separated by ; or -e. By default, sed prints every processed line
unless you suppress output with -n.
The s command: substitution
The most common command:
sed 's/old/new/' file.txt # first match per line
sed 's/old/new/g' file.txt # all matches (global)
sed 's/old/new/2' file.txt # second match only
sed 's/old/new/gi' file.txt # global + case-insensitive
The delimiter does not have to be /, which is convenient for paths:
sed 's|/old/path|/new/path|g' file
sed 's#http://#https://#g' urls.txt
-E for ERE
By default sed uses BRE, like grep without -E. That means +, ?,
(, ), {, } must be escaped. Use -E (or -r in GNU sed):
sed -E 's/(error|warn): (.*)/[\1] \2/' app.log
Back-references \1, \2 work in BRE too, but in BRE the parentheses
must be written \(\).
In-place editing with -i
sed -i 's/foo/bar/g' file.txt # GNU sed
sed -i.bak 's/foo/bar/g' file.txt # with backup file.txt.bak
sed -i '' 's/foo/bar/g' file.txt # macOS BSD sed - empty extension is required
Cross-platform trouble: GNU sed -i accepts an optional suffix
attached directly (sed -i.bak), while BSD sed requires a space and a
mandatory suffix (sed -i '' ...). On macOS, installing gsed via brew
is the usual workaround.
Addressing: where to apply a command
sed '5d' file.txt # delete line 5
sed '5,10d' file # delete lines 5-10
sed '/^#/d' file # delete comment lines
sed '/start/,/end/d' file # delete block from start to end inclusive
sed '$d' file # delete the last line
sed '1,/^$/d' file # delete from the start to the first blank line
sed -n '/ERROR/p' file # print only ERROR lines (like grep)
Address ranges are particularly useful: they let you cut sections out of configs or filter log blocks between markers.
Commands other than s
| Command | What it does |
|---|---|
d | delete (do not print) the current line |
p | print (meaningful with -n) |
q | quit immediately |
= | print the line number |
i\TEXT | insert TEXT before the line |
a\TEXT | append TEXT after the line |
c\TEXT | replace the entire line with TEXT |
y/abc/ABC/ | character-by-character transliteration (like tr) |
r FILE | insert the contents of FILE after the matching line |
w FILE | write matching lines to FILE |
sed '/^server {/i\# auto-managed' nginx.conf # insert comment before each server blocksed -n '5,10p' file # faster than `head -10 | tail -6`
sed '0,/foo/{s/foo/bar/}' file # replace only the first foo in the fileMulti-line: pattern space and hold space
sed works with two buffers:
- pattern space: the current line (the default working area)
- hold space: secondary storage, persists between line iterations
Commands that move data between them:
h: copy pattern to hold (overwrites)H: append pattern to hold (with\n)g: copy hold to pattern (overwrites)G: append hold to patternx: swap
The classic trick for reversing line order (like tac):
sed '1!G;h;$!d' file
At this point [[cmd-awk|awk]] or Python is usually easier to read.
Troubleshooting
sed: -i may not be used with stdin:-irequires a file argument, not a pipe.sed: 1: "...": invalid command codeon macOS: you are running BSD sed. Installbrew install gnu-sedand callgsed.unterminated 's' command: a closing/is missing, or a special character in the pattern is not escaped.- Substitution does nothing, but
grepfinds a match: without-E, sed uses BRE, so(foo|bar)is a literal string, not a group. - In-place editing breaks a symlink: GNU sed renames the file by
default, severing the link. Use
--follow-symlinks. - Encoding breaks: sed works byte by byte. For UTF-8 with multi-byte
character classes like
[[:alpha:]], you need the correct locale (LC_ALL=ru_RU.UTF-8).
Alternatives
awk: when you need field processing, counters, or aggregation.perl -pe/perl -i: PCRE, lookahead, and more predictable cross-platform behavior.sd(Rust): a modern CLI tool with PCRE by default and a cleaner syntax.