Skip to content

/commands/sed/sed-replace-in-file

sed -i 's/old/new/g' file: find and replace text in a file

verified onenvironmentlocal · windows 10.0.26200 arm64 · GNU bash 5.3.15 (Git Bash) · sed (GNU sed) 4.9updated
tool
sed
versions
GNU sed 4.x, BSD sed (macOS)
os
linux, macos, windows

sed -i 's/old/new/g' file replaces every occurrence of old with new in file and saves the file in place. s is the substitute command, g replaces all matches on each line (without it only the first match per line changes), and -i writes the result back to the file instead of printing it. Run the command without -i first to preview the result, or use -i.bak to keep a backup copy.

Example

bash
cat > app.conf <<'EOF'
host = old.example.com
backup_host = old.example.com
url = https://old.example.com/old.example.com
EOF
sed 's/old\.example\.com/new.example.org/' app.conf
echo ===
sed -i.bak 's/old\.example\.com/new.example.org/g' app.conf
cat app.conf
ls app.conf*

Output

host = new.example.org
backup_host = new.example.org
url = https://new.example.org/old.example.com
===
host = new.example.org
backup_host = new.example.org
url = https://new.example.org/new.example.org
app.conf
app.conf.bak

Without g only the first match on each line changed, so the URL kept its second host name. With g every match changed, and -i.bak left the original in app.conf.bak. The dots are escaped because an unescaped . matches any character.

Variants

CommandEffect
sed -i 's/a/b/' fFirst match on each line
sed -i 's/a/b/g' fEvery match
sed -i 's/a/b/2' fOnly the second match on each line
sed -i 's/a/b/gI' fEvery match, ignoring case (GNU)
sed -i '/^#/!s/a/b/g' fOnly on lines that do not start with #
sed -i '10,20s/a/b/g' fOnly on lines 10 to 20
sed -i 's|/usr/local|/opt|g' fAnother delimiter, so slashes need no escaping
sed -E 's/([0-9]+)px/\1rem/g' fExtended regex with a capture group reused as \1
sed -i -e 's/a/b/g' -e 's/c/d/g' fSeveral substitutions in one pass

Replace a value that is in a shell variable

bash
printf 'version=1.2.3\n' > release.txt
new=2.0.0
sed -i "s/^version=.*/version=$new/" release.txt
cat release.txt

Output

version=2.0.0

Double quotes let the shell expand $new before sed runs. If the value can contain /, & or \, those characters have a meaning in the replacement: pick a delimiter that cannot occur and escape & and \ in the value.

GNU vs macOS

TaskGNU sed (Linux, Git Bash)BSD sed (macOS)
In place, no backupsed -i 's/a/b/g' fsed -i '' 's/a/b/g' f
In place with backupsed -i.bak 's/a/b/g' fsed -i.bak 's/a/b/g' f
Case-insensitives/a/b/gINot supported; use [Aa]

-i.bak with no space works on both, which makes it the portable choice for scripts. The page on GNU vs BSD sed lists the other differences.

Pitfalls

  • -i replaces the file with a new one: hard links are broken and a symlink is replaced by a regular file unless GNU --follow-symlinks is given.
  • A missing closing delimiter gives unterminated 's' command, and a stray flag after the last delimiter gives unknown option to 's'.
  • Check the result with diff app.conf.bak app.conf, or git diff in a repository, before deleting the backup.

FAQ

How do I replace text in many files at once?

Let find or grep pick the files and pass them to one sed call, for example grep -rl 'old.example' --include='*.conf' . | xargs sed -i 's/old\.example/new.example/g'. Check the list with grep -rl first, and keep the project under version control so you can review the change with git diff.

Why does sed -i fail on macOS with 'invalid command code'?

BSD sed requires an argument after -i, the backup suffix. sed -i 's/a/b/' file makes it read the script as the suffix and the file name as the script. Write sed -i '' 's/a/b/' file on macOS, or -i.bak, which works on both.

How do I replace a string that contains slashes?

Pick another delimiter, for example sed 's|/usr/local|/opt|g'. Any character after s becomes the delimiter, so the slashes in the paths need no escaping.

Sources