Everything you need to know about sed substitution
The command name sed
is derived from stream editor. The most commonly used editing command is substitution, for which various examples are shown in this blog post.
The examples presented here have been tested with
GNU sed
. Syntax and features might differ for other implementations.
Basic Substitution🔗
The substitute command syntax is s/REGEXP/REPLACEMENT/FLAGS
where:
s
stands for the substitute command/
is an idiomatic delimiter character to separate various portions of the commandREGEXP
is the regular expression that defines the search portionREPLACEMENT
refers to the replacement stringFLAGS
are options to change the default behavior of the command
# for each input line, change only the first ',' to '-'
$ printf '1,2,3,4\na,b,c,d\n' | sed 's/,/-/'
1-2,3,4
a-b,c,d
# you can change all the matches by adding the 'g' flag
$ printf '1,2,3,4\na,b,c,d\n' | sed 's/,/=-=/g'
1=-=2=-=3=-=4
a=-=b=-=c=-=d
Filter and Substitute🔗
You can use line numbers, regular expressions or a combination of them to select lines and then apply a command to these filtered lines. See the Selective editing chapter from my ebook to learn more about the various kinds of addressing.
# make changes only to the first line
$ printf '1,2,3,4\na,b,c,d\n' | sed '1 s/,/::/g'
1::2::3::4
a,b,c,d
# apply substitution only if the input line does NOT contain '2'
$ printf '1,2,3,4\na,b,c,d\n' | sed '/2/! s/,/-/g'
1,2,3,4
a-b-c-d
Regular Expressions🔗
Only a handful of examples are shown here. See this chapter from my ebook for a more detailed discussion. See my blog post for a cheatsheet.
Anchors:
# lines starting with 'par'
$ printf 'car par\nparty\nspare\n' | sed 's/^par/tas/'
car par
tasty
spare
# words starting with 'par'
$ printf 'car par\nparty\nspare\n' | sed 's/\bpar/1234/'
car 1234
1234ty
spare
Alternation and Grouping:
# same as: sed -E 's/part|parrot|parent/X/g'
# -E option enables ERE (default is BRE)
$ echo 'par part parrot parent' | sed -E 's/par(en|ro)?t/X/g'
par X X X
Character Class and Quantifiers:
# numbers >= 100 with optional leading zeros
$ echo '0501 035 154 12 26 98234' | sed -E 's/\b0*[1-9][0-9]{2,}\b/X/g'
X 035 X 12 26 X
# retain only punctuation characters
$ echo ',pie tie#ink-eat_42' | sed -E 's/[^[:punct:]]+//g'
,#-_
Backreferences:
# remove two or more duplicate words separated by spaces
# \b prevents false matches like 'the theatre', 'sand and stone' etc
$ echo 'aa a a a 42 f_1 f_1 f_13.14' | sed -E 's/\b(\w+)( \1)+\b/\1/g'
aa a 42 f_1 f_13.14
# whole words that have at least one consecutive repeated character
$ echo 'effort flee facade oddball rat tool' | sed -E 's/\w*(\w)\1\w*/X/g'
X X facade X rat X
# match lowercase followed by underscore followed by lowercase
# delete the underscore and convert the 2nd lowercase to uppercase
$ echo '_fig aug_price next_line' | sed -E 's/([a-z])_([a-z])/\1\u\2/g'
_fig augPrice nextLine
Replace Specific Occurrences🔗
You can use a number as a flag to replace only that particular occurrence of the search term. If you combine this with the g
flag, all occurrences after that particular match will also be replaced.
$ s='apple:banana:cherry:fig:mango'
# replace only the second occurrence
$ echo "$s" | sed -E 's/[^:]+/"&"/2'
apple:"banana":cherry:fig:mango
# replace all matches except the first occurrence
$ echo "$s" | sed -E 's/:/---/2g'
apple:banana---cherry---fig---mango
With the help of capture groups and backreferences, you can replace a specific occurrence from the end of the input line.
$ s='car,art,pot,map,urn,ray,ear'
# replace the last occurrence
$ echo "$s" | sed -E 's/(.*),/\1[]/'
car,art,pot,map,urn,ray[]ear
# generic version, where {N} refers to last but Nth occurrence
$ echo "$s" | sed -E 's/(.*),((.*,){3})/\1[]\2/'
car,art,pot[]map,urn,ray,ear
Executing External Commands🔗
The e
flag helps to insert the output of a shell command within sed
.
# replace the entire line with the output of a shell command
$ printf 'apple\nreplace this line\n' | sed 's/^replace.*/date/e'
apple
Wednesday 07 May 2025 10:25:34 AM IST
# after substitution, the command that gets executed is 'seq 3'
$ echo 'xyz 3' | sed 's/xyz/seq/e'
1
2
3
Different Delimiters🔗
The /
character is idiomatically used as the REGEXP delimiter. But any character other than \
and the newline character can be used instead.
# instead of this
$ echo '/home/learnbyexample/reports' | sed 's/\/home\/learnbyexample\//~\//'
~/reports
# use a different delimiter
$ echo '/home/learnbyexample/reports' | sed 's#/home/learnbyexample/#~/#'
~/reports
In-place Editing🔗
The -i
option is helpful to write back the changes to the original files itself. If you don't provide an argument to this option, backup of the original file won't be created.
$ cat colors.txt
deep blue
light orange
blue delight
# no output on terminal as the -i option is used
$ sed -i.bkp 's/blue/green/' colors.txt
# output from sed is written back to 'colors.txt'
$ cat colors.txt
deep green
light orange
green delight
# original file is preserved in 'colors.txt.bkp'
$ cat colors.txt.bkp
deep blue
light orange
blue delight
*
in the argument to the -i
option will be replaced with the input filename. So, -i'bkp.*'
for f1.txt
will create bkp.f1.txt
as the backup. And if you use old/*
, the backups will be under the same name but under the directory old
(provided that directory already exists).
Manipulating Newlines🔗
By default, sed
reads the input line by line (with \n
considered as the line ending). The newline character, if present, is removed and then added back when the pattern space is printed. Which implies that you cannot directly manipulate the newline character, unless you use features that results in more than one line in the pattern space.
# append the next line to the pattern space
# and then replace newline character with a colon character
$ seq 7 | sed 'N; s/\n/:/'
1:2
3:4
5:6
7
# if line contains 'at', the next line gets appended to the pattern space
# then the substitution is performed on the two lines in the buffer
$ printf 'gates\nnot\nused\n' | sed '/at/{N; s/s\nnot/d/}'
gated
used
Slurping Input🔗
If the input doesn't have NUL characters, then the -z
option is handy to process the entire input as a single string. This is effective only for files small enough to fit the available machine memory. It would also depend on the regular expression, as some patterns have exponential relationship with respect to the data size.
# add ; to the previous line if the current line starts with c
$ printf 'cater\ndog\ncoat\ncutter\nmat\n' | sed -z 's/\nc/;&/g'
cater
dog;
coat;
cutter
mat
Fixed String Substitution🔗
Typically, you'd need to escape \
, &
and the delimiter for the string used in the replacement section. For the search section, the characters to be escaped will depend upon whether you are using BRE or ERE.
# replacement string
$ r='a/b&c\d'
$ r=$(printf '%s' "$r" | sed 's#[\&/]#\\g')
# ERE version for the search string
$ s='{[(\ta^b/d).*+?^$|]}'
$ s=$(printf '%s' "$s" | sed 's#[{[()^$*?+.\|/]#\\g')
$ printf '%s\n' 'f*{[(\ta^b/d).*+?^$|]} - 3' | sed -E 's/'"$s"'/'"$r"'/g'
f*a/b&c\d - 3
# BRE version for the search string
$ s='{[(\ta^b/d).*+?^$|]}'
$ s=$(printf '%s' "$s" | sed 's#[[^$*.\/]#\\g')
$ printf '%s\n' 'f*{[(\ta^b/d).*+?^$|]} - 3' | sed 's/'"$s"'/'"$r"'/g'
f*a/b&c\d - 3
See my blog post for multiline fixed string substitution examples.
Programming ebooks🔗
Check out my ebooks on Regular Expressions, Linux CLI tools, Python and Vim. You can get them all as a single bundle via leanpub or gumroad.