/commands/sed/sed-replace-in-file
sed -i 's/old/new/g' file: find and replace text in a file
- 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
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
| Command | Effect |
|---|---|
| sed -i 's/a/b/' f | First match on each line |
| sed -i 's/a/b/g' f | Every match |
| sed -i 's/a/b/2' f | Only the second match on each line |
| sed -i 's/a/b/gI' f | Every match, ignoring case (GNU) |
| sed -i '/^#/!s/a/b/g' f | Only on lines that do not start with # |
| sed -i '10,20s/a/b/g' f | Only on lines 10 to 20 |
| sed -i 's|/usr/local|/opt|g' f | Another delimiter, so slashes need no escaping |
| sed -E 's/([0-9]+)px/\1rem/g' f | Extended regex with a capture group reused as \1 |
| sed -i -e 's/a/b/g' -e 's/c/d/g' f | Several substitutions in one pass |
Replace a value that is in a shell variable
printf 'version=1.2.3\n' > release.txt
new=2.0.0
sed -i "s/^version=.*/version=$new/" release.txt
cat release.txtOutput
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
| Task | GNU sed (Linux, Git Bash) | BSD sed (macOS) |
|---|---|---|
| In place, no backup | sed -i 's/a/b/g' f | sed -i '' 's/a/b/g' f |
| In place with backup | sed -i.bak 's/a/b/g' f | sed -i.bak 's/a/b/g' f |
| Case-insensitive | s/a/b/gI | Not 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
-ireplaces the file with a new one: hard links are broken and a symlink is replaced by a regular file unless GNU--follow-symlinksis given.- A missing closing delimiter gives
unterminated 's' command, and a stray flag after the last delimiter givesunknown option to 's'. - Check the result with
diff app.conf.bak app.conf, orgit diffin 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.