How to Use sed to Find and Replace Strings in Files

By 

Updated on

13 min read

Using the sed command to find and replace strings in a Linux file

When working with text files, you will often need to find and replace strings of text in one or more files.

sed is a stream editor. It can perform basic text manipulation on files and input streams such as pipelines. With sed, you can search, find and replace, insert, and delete words and lines . It supports basic and extended regular expressions that allow you to match complex patterns.

This guide explains how to use sed to find and replace strings in files, covering the most common options, flags, and patterns.

To preview a find-and-replace operation before changing the file, use the s (substitute) command without -i:

Terminal
sed 's/old-string/new-string/g' file.txt

This prints the edited content to the terminal without modifying file.txt. When the output looks correct, add -i.bak to edit the file in place and keep a backup, or use -i if you do not need one.

Find and Replace Strings with sed

There are several versions of sed, with some functional differences between them. macOS uses the BSD version, while most Linux distributions come with GNU sed pre-installed by default. The examples in this guide use GNU sed.

The general syntax for searching and replacing text with sed is:

txt
sed -i 's/SEARCH_REGEX/REPLACEMENT/g' INPUTFILE
  • -i - By default, sed writes output to standard output. This option tells sed to edit the file in place. If an extension is supplied (for example -i.bak), a backup of the original file is created.
  • s - The substitute command, the most commonly used sed command.
  • / / / - Delimiter character. It can be any character, but / is the most common choice.
  • SEARCH_REGEX - A plain string or regular expression to search for.
  • REPLACEMENT - The replacement string.
  • g - Global replacement flag. By default, sed replaces only the first match on each line. When this flag is set, all matches are replaced.
  • INPUTFILE - The name of the file to operate on.

It is good practice to quote the expression so that shell meta-characters are not expanded.

For demonstration purposes, the examples below use the following file:

file.txttxt
123 Foo foo foo
foo /bin/bash Ubuntu foobar 456

The examples in this section leave out -i, so sed prints the result to the terminal and file.txt stays untouched. Each command is safe to run more than once, and every example below starts from the same original file.

If the g flag is omitted, only the first match on each line is replaced:

Terminal
sed 's/foo/linux/' file.txt
output
123 Foo linux foo
linux /bin/bash Ubuntu foobar 456

sed replaced only the first foo on each line and left the rest alone.

With the global replacement flag, sed replaces all occurrences of the search pattern:

Terminal
sed 's/foo/linux/g' file.txt
output
123 Foo linux linux
linux /bin/bash Ubuntu linuxbar 456

Notice that the substring foo inside foobar is also replaced. Foo on the first line keeps its capital F, since the match is case-sensitive by default. To match only whole words and avoid partial matches, use the word-boundary expression \b at both ends of the search string:

Terminal
sed 's/\bfoo\b/linux/g' file.txt
output
123 Foo linux linux
linux /bin/bash Ubuntu foobar 456

foobar survives this time, since the foo inside it is not followed by a word boundary.

To make the pattern case-insensitive, use the I flag. The following example uses both g and I:

Terminal
sed 's/foo/linux/gI' file.txt
output
123 linux linux linux
linux /bin/bash Ubuntu linuxbar 456

Foo at the start of the first line is now replaced along with the rest.

If the search string contains the delimiter character (/), escape each slash with a backslash. For example, to replace /bin/bash with /usr/bin/zsh:

Terminal
sed 's/\/bin\/bash/\/usr\/bin\/zsh/g' file.txt

A cleaner approach is to use a different delimiter such as | or ::

Terminal
sed 's|/bin/bash|/usr/bin/zsh|g' file.txt
output
123 Foo foo foo
foo /usr/bin/zsh Ubuntu foobar 456

Both forms produce the same result, but the second one stays readable when the paths get longer.

sed also supports regular expressions. To replace all 3-digit numbers with the string number, run:

Terminal
sed 's/\b[0-9]\{3\}\b/number/g' file.txt
output
number Foo foo foo
foo /bin/bash Ubuntu foobar number

Both 123 and 456 matched. The \{3\} quantifier asks for exactly three digits.

The ampersand character & represents the matched pattern and can be used in the replacement string. For example, to wrap each 3-digit number in curly braces:

Terminal
sed 's/\b[0-9]\{3\}\b/{&}/g' file.txt
output
{123} Foo foo foo
foo /bin/bash Ubuntu foobar {456}

Each number is preserved and only the braces are added around it.

Editing Files in Place

Every example so far printed to the terminal and left file.txt alone. Once the output looks correct, add the -i option to write the result back into the file:

Terminal
sed -i 's/foo/linux/g' file.txt

This time sed prints nothing at all. The edit goes straight into file.txt, and there is no undo. Run the command once without -i first.

It is always a good idea to create a backup before editing a file in place. To do this, provide a backup extension to the -i option. The following command edits file.txt and saves the original as file.txt.bak:

Terminal
sed -i.bak 's/foo/linux/g' file.txt

To confirm the backup was created, list the files with ls :

Terminal
ls
output
file.txt  file.txt.bak

Replace Text on a Specific Line

To limit a substitution to a specific line number, place the line address before the s command:

Terminal
sed '2s/foo/linux/' file.txt
output
123 Foo foo foo
linux /bin/bash Ubuntu foobar 456

The first line contains foo but is untouched. The 2 address restricted the substitution to the second line.

To apply the replacement across a range of lines, use start,end before the s command:

Terminal
sed '1,2s/foo/linux/g' file.txt
output
123 Foo linux linux
linux /bin/bash Ubuntu linuxbar 456

Replace an Entire Line

The examples above swap one piece of text inside a line. To replace a whole line regardless of what it contains, use the c (change) command with a line number:

Terminal
sed '2c\Replaced the entire second line' file.txt
output
123 Foo foo foo
Replaced the entire second line

The same works with a pattern address, which is useful for configuration files where the line number is not fixed. This rewrites every line containing Ubuntu:

Terminal
sed '/Ubuntu/c\PRETTY_NAME="Debian GNU/Linux 13"' file.txt
output
123 Foo foo foo
PRETTY_NAME="Debian GNU/Linux 13"

The replacement text runs to the end of the line, so slashes inside it need no escaping. Writing c\ and the text on one line is a GNU extension; BSD sed on macOS reports extra characters after \ at the end of c command and expects the text on the following line.

You can also replace a line with the substitute command by matching everything on it with .*:

Terminal
sed '2s/.*/Replaced the entire second line/' file.txt
output
123 Foo foo foo
Replaced the entire second line

The s form can also keep part of the original line. A capture group carries the kept portion into the replacement.

Replace Only on Lines Matching a Pattern

Line numbers are fragile when a file changes. A pattern address is often a better filter: put a regular expression between slashes before the s command, and sed runs the substitution only on the lines that match.

This replaces foo with linux, but only on lines that also contain Ubuntu:

Terminal
sed '/Ubuntu/s/foo/linux/g' file.txt
output
123 Foo foo foo
linux /bin/bash Ubuntu linuxbar 456

The first line does not contain Ubuntu, so sed skipped it. Add ! after the address to invert it and act on everything except the matching lines:

Terminal
sed '/Ubuntu/!s/foo/linux/g' file.txt
output
123 Foo linux linux
foo /bin/bash Ubuntu foobar 456

Replace the Nth Occurrence

By default, sed replaces the first match on each line. To replace only the Nth match, pass the occurrence number as a flag. For example, to replace only the second occurrence of foo per line:

Terminal
sed 's/foo/linux/2' file.txt
output
123 Foo foo linux
foo /bin/bash Ubuntu linuxbar 456

The count restarts on every line and includes partial matches. On the second line the first foo stands on its own and the second one sits inside foobar. That is why foobar became linuxbar.

To replace all occurrences starting from the Nth, combine the occurrence number with the g flag:

Terminal
sed 's/foo/linux/2g' file.txt
output
123 Foo foo linux
foo /bin/bash Ubuntu linuxbar 456

Both lines hold exactly two matches here, so the result matches the previous command. On a line with more matches, everything from the second one onwards would be replaced. This combination is a GNU extension: BSD sed on macOS rejects it with more than one number or 'g' in substitute flags.

Capture Groups in the Replacement

The & character reuses the whole match. Capture groups go further and let you reuse parts of it. Wrap a portion of the pattern in parentheses, then refer back to it in the replacement as \1, \2, and so on.

Basic regular expressions require escaped parentheses, and that gets noisy quickly. The -E option switches to extended regular expressions , where parentheses and the + quantifier work without backslashes. This swaps the two halves of a name and version pair:

Terminal
echo "ubuntu-2404" | sed -E 's/([a-z]+)-([0-9]+)/\2-\1/'
output
2404-ubuntu

Capture groups also let you rewrite part of a line and keep the rest. Here the directory is captured and reused, so only the shell name changes:

Terminal
sed -E 's|(/bin/)bash|\1zsh|' file.txt
output
123 Foo foo foo
foo /bin/zsh Ubuntu foobar 456

GNU sed adds case conversion in the replacement. \U uppercases everything that follows it:

Terminal
sed -E 's/(Ubuntu)/\U\1/' file.txt
output
123 Foo foo foo
foo /bin/bash UBUNTU foobar 456

BSD sed does not support \U and prints a literal U instead, so keep this one for Linux.

Multiple Substitutions in One Command

Piping one sed call into another works, but it is easier to pass several expressions to a single command. Separate them with semicolons:

Terminal
sed 's/foo/linux/g; s/Ubuntu/Debian/' file.txt
output
123 Foo linux linux
linux /bin/bash Debian linuxbar 456

The -e option does the same thing and stays readable when the expressions get long:

Terminal
sed -e 's/foo/linux/g' -e 's/Ubuntu/Debian/' file.txt

For a larger set of edits, put the commands in a file, one per line:

rules.sedsh
s/foo/linux/g
s/Ubuntu/Debian/

Then pass the file with the -f option:

Terminal
sed -f rules.sed file.txt
output
123 Foo linux linux
linux /bin/bash Debian linuxbar 456

A script file also keeps the edits under version control. That helps when the same replacements run again later.

Shell Variables in the Search Pattern

Single quotes stop the shell from expanding anything inside them, so a variable written as $new reaches sed literally. Switch to double quotes when you want the shell to substitute the value first:

Terminal
old="foo"
new="linux"
sed "s/$old/$new/g" file.txt
output
123 Foo linux linux
linux /bin/bash Ubuntu linuxbar 456

Double quotes come with a catch. The shell also expands backslashes, backticks, and $ inside them, and any / in the value will end the expression early and break the command. When the value is a path, pick a delimiter that cannot appear in it:

Terminal
newshell="/usr/bin/zsh"
sed "s|/bin/bash|$newshell|" file.txt
output
123 Foo foo foo
foo /usr/bin/zsh Ubuntu foobar 456

Avoid naming the variable path in this situation. In zsh that name is tied to PATH, and assigning a single value to it will break command lookup for the rest of the session.

Recursive Find and Replace

To recursively replace a string across all files in a directory, combine sed with find or grep .

The following command searches all files in the current working directory and passes them to sed:

Terminal
find . -type f -exec sed -i 's/foo/bar/g' {} +

To handle filenames that contain spaces, use the -print0 option with xargs -0 :

Terminal
find . -type f -print0 | xargs -0 sed -i 's/foo/bar/g'

To limit the replacement to files with a specific extension:

Terminal
find . -type f -name "*.md" -print0 | xargs -0 sed -i 's/foo/bar/g'

To exclude hidden directories and files starting with a dot:

Terminal
find . -type f -not -path '*/\.*' -print0 | xargs -0 sed -i 's/foo/bar/g'

To replace only in files that actually contain the search string, use grep to filter first:

Terminal
grep -rlZ 'foo' . | xargs -0 sed -i.bak 's/foo/bar/g'

This last form also avoids rewriting files that do not need it. Their modification times stay intact.

Quick Reference

For a printable quick reference, see the Sed cheatsheet .

Option / FlagDescription
s/SEARCH/REPLACE/Replace first match per line
s/SEARCH/REPLACE/gReplace all matches (global)
s/SEARCH/REPLACE/ICase-insensitive match
s/SEARCH/REPLACE/2Replace only the 2nd occurrence per line
s/SEARCH/REPLACE/2gReplace from the 2nd occurrence onwards (GNU only)
\bWord boundary, prevents partial word matches
&Reference to the matched pattern in replacement
-iEdit file in place
-i.bakEdit in place and save backup with .bak extension
Ns/.../.../Limit substitution to line N
N,Ms/.../.../Limit substitution to lines N through M
/pattern/s/.../.../Substitute only on lines matching the pattern
/pattern/!s/.../.../Substitute only on lines not matching the pattern
Nc\TEXTReplace line N entirely
-EUse extended regular expressions
\1, \2Reuse a capture group in the replacement
\U, \EUppercase replacement text, end case conversion (GNU only)
-e 'cmd1' -e 'cmd2'Run several expressions in one command
-f script.sedRead commands from a file

Troubleshooting

sed: 1: "file.txt": invalid command code f on macOS
macOS ships with BSD sed, where -i always requires a backup extension. Without one, sed reads the filename as part of the script and fails. The exact wording depends on the first character of the filename. A name starting with f reports invalid command code f. A name starting with b, such as backup.txt, reports undefined label 'ackup.txt'. To edit in place without keeping a backup on macOS, pass an empty string: sed -i '' 's/foo/bar/g' file.txt.

Pattern matches more than expected
Special regex characters such as ., *, [, and \ have special meaning in sed patterns. Escape them with a backslash if you want to match them literally. For example, to match a literal dot use \. instead of ..

No matches found despite the string being present
Check the delimiter character. If the search string contains /, either escape each slash with \ or switch to a different delimiter such as | or :. Also verify the file encoding; sed may not handle non-UTF-8 files as expected.

Command works on Linux but fails on macOS
Several forms in this guide are GNU extensions, including \b, the I flag, Ng, \U, and a one-line c\ command. Install GNU sed with brew install gnu-sed and call it as gsed to get the same behavior on macOS.

FAQ

How do I preview sed changes without modifying the file?
Run sed without the -i flag. The result is written to standard output and the original file is left unchanged.

Why does sed -i not work on macOS?
macOS uses BSD sed, which requires either a backup extension or an empty string after -i: sed -i '' 's/foo/bar/g' file.txt. On Linux, GNU sed accepts -i without an argument.

How do I replace a string that contains forward slashes?
Use a different delimiter character such as | or :. For example: sed 's|/bin/bash|/usr/bin/zsh|g' file.txt.

Does sed rewrite the file when nothing matches?
Yes. With -i, sed writes the file out again even if no substitution took place. That updates its modification time. Filter with grep -rl first when that matters.

Conclusion

Most find-and-replace work with sed comes down to one substitute command: match with a regular expression, decide how many occurrences to touch with g or a number, and narrow the scope with a line number or a pattern address. Run it once without -i to check the result, then add -i.bak when the output looks right. To remove lines instead of rewriting them, see our guide on deleting lines with sed .

Linuxize Weekly Newsletter

A quick weekly roundup of new tutorials, news, and tips.

About the authors

Dejan Panovski

Dejan Panovski

Dejan Panovski is the founder of Linuxize, an RHCSA-certified Linux system administrator and DevOps engineer based in Skopje, Macedonia. Author of 1000+ Linux tutorials with 20+ years of experience turning complex Linux tasks into clear, reliable guides.

View author page