500

How do I edit a file in a single sed command? Currently, I have to manually stream the edited content into a new file and then rename the new file to the original file name.

I tried sed -i, but my Solaris system said that -i is an illegal option. Is there a different way?

Mateen Ulhaq
  • 24,552
  • 19
  • 101
  • 135
amphibient
  • 29,770
  • 54
  • 146
  • 240
  • 11
    `-i` is an option in gnu sed, but is not in standard sed. However, it streams the content to a new file and then renames the file so it is not what you want. – William Pursell Oct 02 '12 at 18:58
  • It's not possibly to satisfy all your constraints. You could use `ed` rather than `sed`, or you stream via a memory buffer using `sed "$f" | sponge "$f"`, or you could install GNU sed. Which of those best addresses your particular situation is beyond our knowledge (for one thing, it probably depends on the size of file you are processing). – Toby Speight Nov 14 '16 at 16:40

15 Answers15

810

The -i option streams the edited content into a new file and then renames it behind the scenes, anyway.

Example:

sed -i 's/STRING_TO_REPLACE/STRING_TO_REPLACE_IT/g' filename

while on macOS you need:

sed -i '' 's/STRING_TO_REPLACE/STRING_TO_REPLACE_IT/g' filename
choroba
  • 231,213
  • 25
  • 204
  • 289
  • 6
    at least it does it for me so i don't have to – amphibient Oct 02 '12 at 18:47
  • 2
    You might try to compile the GNU sed on your system, then. – choroba Oct 02 '12 at 18:48
  • 4
    example: `sed -i "s/STRING_TO_REPLACE/STRING_TO_REPLACE_IT/g" ` – Thales Ceolin Apr 17 '14 at 13:30
  • 2
    This is better than the perl solution. Somehow it doesnt create any .swap file .... thanks! – maths Jul 03 '14 at 00:51
  • 4
    @maths: It does, but maybe somewhere else or for a shorter time? – choroba Jul 03 '14 at 05:38
  • +1 for " The -i option streams the edited content into a new file and then renames it behind the scenes" – Yogesh Yadav Dec 17 '15 at 04:32
  • 3
    on Yocto build system following example works: `sed -i -e 's,STRING with SPACES,STRING to REPLACE with,g' filename` - i.e. important are commas `,` instead of slashes `/` – Oleg Kokorin Apr 29 '18 at 21:17
  • 1
    Any way to make this work cross-platform with the same syntax? – William Entriken Oct 25 '18 at 03:21
  • @WilliamEntriken: Create a tmp file explicitely, rename it afterwards. – choroba Oct 25 '18 at 07:24
  • I was having issues with Git on Windows thinking the entire file had changed. Make sure that if you go this route you check the line endings. – dimwittedanimal Dec 20 '18 at 18:09
  • @WilliamEntriken -- @Oleg Kokorin's response above seems to work on Mac. I.e. use `sed -i -e 's/blah/blah/g'`. – Baxissimo Mar 05 '19 at 21:23
  • The Unix tag means `-i` is not readily available. It is certainly not available on Sun's `sed`. – jww Oct 28 '19 at 16:56
  • 5
    +1 for mac, this is probably the 3rd time (over the last 4 years) I forgot that it needs an extra par of quotes and had to return to this post. But in my defense 95% of the times I use my mac to just ssh into a linux box. – Ali May 01 '20 at 13:06
  • 2
    A major benefit of `-i` is that's it much faster than shell redirection. On my machine, I went from 5MB/s to ~12MB/s when writing to a spinning disk. – Eli_B Sep 15 '20 at 20:58
  • 1
    Is there a portable way? (so no OS-specific deviations) I want to embedded to a POSIX 2017 compatible script, so it changes the content of original file / creates a new file with same fileName at a different dir and I could `mv` back and `chmod` to match the old file permission – jimmymcheung Dec 18 '22 at 15:51
87

On a system where sed does not have the ability to edit files in place, I think the better solution would be to use perl:

perl -pi -e 's/foo/bar/g' file.txt

Although this does create a temporary file, it replaces the original because an empty in place suffix/extension has been supplied.

Steve
  • 51,466
  • 13
  • 89
  • 103
  • 20
    Not only does it create a temporary file, it also breaks hard links (eg, instead of changing the contents of the file, it replaces the file with a new file of the same name.) Sometimes this is the desired behavior, sometimes it is acceptable, but when there are multiple links to a file it is almost always the wrong behavior. – William Pursell Oct 03 '12 at 11:37
  • 2
    Not all systems have perl and this is where piping comes into play. See my answer below. – Dwight Spencer Feb 19 '14 at 23:06
  • 3
    @DwightSpencer: I believe that all systems without Perl are broken. A single command trumps a chain of commands when one wants elevated permissions and must use `sudo`. In my mind, the question is about how to avoid manually creating and renaming a temporary file without having to install the latest GNU or BSD `sed`. Just use Perl. – Steve Feb 20 '14 at 00:42
  • Not an answer to the original question. – Petr Peller Jul 27 '14 at 18:00
  • 5
    @PetrPeller: Really? If you read the question carefully, you would understand that the OP is trying to avoid something like, `sed 's/foo/bar/g' file.txt > file.tmp && mv file.tmp file.txt`. Just because in-place editing does the rename using a temporary file, doesn't mean that he/she _must_ perform a manual rename when the option is not available. There are other tools out there that can do what he/she wants, and Perl is the obvious choice. How is this not an answer to the original question? – Steve Aug 01 '14 at 02:54
  • Downvoted because the answer is too opinionated. OP asked how to do it with sed. – a_arias Oct 13 '14 at 23:56
  • 3
    @a_arias: You're right; he did. But he can't achieve what he wants using Solaris `sed`. The question was actually a very good one, but it's value has been diminished by people who either can't read man pages or can't be bothered to _actually_ read questions here on SO. – Steve Oct 14 '14 at 01:28
  • 1
    "and Perl is the obvious choice" ... - I respectfully suggest Perl is no longer the obvious choice (although it once was). Perl has been obviated by Python and Ruby, IMO. While we're at it, the OP wanted a *sed* solution. – EdwardG Jul 22 '15 at 18:41
  • 4
    @EdwardGarson: IIRC, Solaris ships with Perl but not Python or Ruby. And like I've said before, the OP can't achieve the desired outcome using Solaris sed. – Steve Jul 23 '15 at 01:25
  • 1
    @Steve - thanks for the heads up, I missed the Solaris requirement. – EdwardG Jul 23 '15 at 19:02
82

Note that on OS X you might get strange errors like "invalid command code" or other strange errors when running this command. To fix this issue try

sed -i '' -e "s/STRING_TO_REPLACE/STRING_TO_REPLACE_IT/g" <file>

This is because on the OSX version of sed, the -i option expects an extension argument so your command is actually parsed as the extension argument and the file path is interpreted as the command code. Source: https://stackoverflow.com/a/19457213

Community
  • 1
  • 1
diadyne
  • 4,038
  • 36
  • 28
  • 5
    This is another oddity of OS X. Fortunately, a `brew install gnu-sed` along with a `PATH` will allow you to use the GNU version of sed. Thanks for mentioning; a definite head scratcher. – Doug Jul 03 '19 at 13:28
34

The following works fine on my mac

sed -i.bak 's/foo/bar/g' sample

We are replacing foo with bar in sample file. Backup of original file will be saved in sample.bak

For editing inline without backup, use the following command

sed -i'' 's/foo/bar/g' sample
minhas23
  • 9,291
  • 3
  • 58
  • 40
20

One thing to note, sed cannot write files on its own as the sole purpose of sed is to act as an editor on the "stream" (ie pipelines of stdin, stdout, stderr, and other >&n buffers, sockets and the like). With this in mind you can use another command tee to write the output back to the file. Another option is to create a patch from piping the content into diff.

Tee method

sed '/regex/' <file> | tee <file>

Patch method

sed '/regex/' <file> | diff -p <file> /dev/stdin | patch

UPDATE:

Also, note that patch will get the file to change from line 1 of the diff output:

Patch does not need to know which file to access as this is found in the first line of the output from diff:

$ echo foobar | tee fubar

$ sed 's/oo/u/' fubar | diff -p fubar /dev/stdin
*** fubar   2014-03-15 18:06:09.000000000 -0500
--- /dev/stdin  2014-03-15 18:06:41.000000000 -0500
***************
*** 1 ****
! foobar
--- 1 ----
! fubar

$ sed 's/oo/u/' fubar | diff -p fubar /dev/stdin | patch
patching file fubar
jww
  • 97,681
  • 90
  • 411
  • 885
Dwight Spencer
  • 1,472
  • 16
  • 22
  • 3
    `/dev/stdin 2014-03-15`, *answered Jan 7* - are you a time traveller? – Adrian Frühwirth Apr 17 '14 at 13:55
  • On Windows, using msysgit, `/dev/stdin` doesn't exist, so you have to replace `/dev/stdin` with '-', a single hyphen without the quotes, so the following commands should work: `$ sed 's/oo/u/' fubar | diff -p fubar -` and `$ sed 's/oo/u/' fubar | diff -p fubar - | patch` – Jim Raden Nov 06 '14 at 16:30
  • @AdrianFrühwirth I do have a blue police box somewhere's around and for some reason they do call me the Doctor at work. But honestly, looks like there's really bad clock drift on the system at the time. – Dwight Spencer Apr 28 '16 at 16:29
  • 3
    These solutions look to me relying on particular buffering behavior. I suspect for bigger files these can break as while sed is reading, the file may start changing underneath by the patch command, or even worse by the tee command that will truncate the file. Actually I'm not sure if `patch` will not truncate as well. I think saving output to another file and then `cat`-ing into original would be safer. – akostadinov May 26 '16 at 12:26
  • real in-place answer from @william-pursell – akostadinov May 26 '16 at 13:09
  • Note that on BSD, -i also changes the behavior when there are multiple input files: they are processed independently (so e.g. $ matches the last line of each file). Also the inplace flag does not work on BusyBox and Solaris, nor reliability under MacRelix/Cygwin. Plus inplace sed itself is relying on buffering behavior as well. In all cases there's no file lock so files could be changed before the sed, diff/patch or any of the standard unix tools resolve and write to the file. If one is that concerned then one should just use a fifo. As for patch it does not truncate files at all. – Dwight Spencer May 27 '16 at 13:40
17

Versions of sed that support the -i option for editing a file in place write to a temporary file and then rename the file.

Alternatively, you can just use ed. For example, to change all occurrences of foo to bar in the file file.txt, you can do:

echo ',s/foo/bar/g; w' | tr \; '\012' | ed -s file.txt

Syntax is similar to sed, but certainly not exactly the same.

Even if you don't have a -i supporting sed, you can easily write a script to do the work for you. Instead of sed -i 's/foo/bar/g' file, you could do inline file sed 's/foo/bar/g'. Such a script is trivial to write. For example:

#!/bin/sh
IN=$1
shift
trap 'rm -f "$tmp"' 0
tmp=$( mktemp )
<"$IN" "$@" >"$tmp" && cat "$tmp" > "$IN"  # preserve hard links

should be adequate for most uses.

William Pursell
  • 204,365
  • 48
  • 270
  • 300
  • 1
    so for example, how would you name this script and how would you call it? – amphibient Oct 02 '12 at 21:00
  • 2
    I would call it `inline` and invoke it as describe above: `inline inputfile sed 's/foo/bar/g'` – William Pursell Oct 02 '12 at 21:11
  • 2
    wow, `ed` only answer that really does in-place. First time I ever need `ed`. Thank you. A little optimization is to use `echo -e ",s/foo/bar/g\012 w"` or `echo $',s/foo/bar/g\012 w'` where shell allows it to avoid extra `tr` call. – akostadinov May 26 '16 at 13:08
  • 4
    `ed` doesn't really do the modifications in-place. From `strace` output, GNU `ed` creates a temp file, writes the changes to the temp file, then rewrites the entire original file, preserving hard links. From `truss` output, Solaris 11 `ed` also uses a temp file, but renames the temp file to the original file name upon saving with the `w` command, destroying any hard links. – Andrew Henle Jan 19 '18 at 11:06
  • 1
    @AndrewHenle That's discouraging. The `ed` that ships with current macos (Mojave, whatever that marketing jargon means) still preserves hardlinks. YMMV – William Pursell Dec 02 '19 at 13:49
15

You could use vi

vi -c '%s/foo/bar/g' my.txt -c 'wq'
mench
  • 360
  • 4
  • 13
10

sed supports in-place editing. From man sed:

-i[SUFFIX], --in-place[=SUFFIX]

    edit files in place (makes backup if extension supplied)

Example:

Let's say you have a file hello.txtwith the text:

hello world!

If you want to keep a backup of the old file, use:

sed -i.bak 's/hello/bonjour' hello.txt

You will end up with two files: hello.txt with the content:

bonjour world!

and hello.txt.bak with the old content.

If you don't want to keep a copy, just don't pass the extension parameter.

S. A.
  • 3,714
  • 2
  • 20
  • 31
  • 4
    The OP specifically mentions that his platform does not support this (popular but) nonstandard option. – tripleee Jan 18 '16 at 20:08
5

If you are replacing the same amount of characters and after carefully reading “In-place” editing of files...

You can also use the redirection operator <> to open the file to read and write:

sed 's/foo/bar/g' file 1<> file

See it live:

$ cat file
hello
i am here                           # see "here"
$ sed 's/here/away/' file 1<> file  # Run the `sed` command
$ cat file
hello
i am away                           # this line is changed now

From Bash Reference Manual → 3.6.10 Opening File Descriptors for Reading and Writing:

The redirection operator

[n]<>word

causes the file whose name is the expansion of word to be opened for both reading and writing on file descriptor n, or on file descriptor 0 if n is not specified. If the file does not exist, it is created.

fedorqui
  • 275,237
  • 103
  • 548
  • 598
  • This corrupted by file. I am not sure of the reason behind it. https://paste.fedoraproject.org/462017/ – Nehal J Wani Oct 27 '16 at 19:09
  • See lines [43-44], [88-89], [135-136] – Nehal J Wani Oct 27 '16 at 19:15
  • 3
    This blogpost explains why this answer should not be used. http://backreference.org/2011/01/29/in-place-editing-of-files/ – Nehal J Wani Oct 27 '16 at 19:22
  • @NehalJWani I will have a long read to the article, thanks for the heads-up! What I did not mention in the answer is that this works if it changes the same amount of characters. – fedorqui Oct 28 '16 at 06:51
  • @NehalJWani this being said, I am sorry if my answer caused harm on your files. I will update it with corrections (or delete it if necessary) after reading the links you provided. – fedorqui Oct 28 '16 at 13:04
  • @NehalJWani updated with a warning on the top. Thanks for the link to this very thorough article. – fedorqui Nov 14 '16 at 14:15
4

Like Moneypenny said in Skyfall: "Sometimes the old ways are best." Kincade said something similar later on.

$ printf ',s/false/true/g\nw\n' | ed {YourFileHere}

Happy editing in place. Added '\nw\n' to write the file. Apologies for delay answering request.

Paolo
  • 21,270
  • 6
  • 38
  • 69
jlettvin
  • 1,113
  • 7
  • 13
  • Can you explain what this does? – Matt Montag Jan 16 '18 at 07:53
  • 2
    It invokes ed(1), the text editor with some characters written to stdin. As someone under the age of 30, I think it's okay for me to say that ed(1) is to dinosaurs as dinosaurs are to us. Anyway, instead of opening ed and typing that stuff in, jlettvin is piping the characters in using `|`. @MattMontag – Ari Sweedler Aug 14 '18 at 16:13
  • If you're asking what the commands do, there's two of them, terminated by a `\n`. [range]s/// is the form of the substitute command - a paradigm still seen in many other text editors (okay, vim, a direct descendent of ed) Anyway, the `g` flag stands for "global", and means "Every instance on each line that you look at". The range `3,5` looks at lines 3-5. `,5` looks at StartOfFile-5, `3,` looks at lines 3-EndOfFile and `,` looks at StartOfFile-EndOfFile. A global substitue command on every line that replaces "false" with "true". Then, the write command, `w`, is entered. – Ari Sweedler Aug 14 '18 at 16:17
3

You didn't specify what shell you are using, but with zsh you could use the =( ) construct to achieve this. Something along the lines of:

cp =(sed ... file; sync) file

=( ) is similar to >( ) but creates a temporary file which is automatically deleted when cp terminates.

Thor
  • 45,082
  • 11
  • 119
  • 130
3
mv file.txt file.tmp && sed 's/foo/bar/g' < file.tmp > file.txt

Should preserve all hardlinks, since output is directed back to overwrite the contents of the original file, and avoids any need for a special version of sed.

tripleee
  • 175,061
  • 34
  • 275
  • 318
JJM
  • 31
  • 1
2

To resolve this issue on Mac I had to add some unix functions to core-utils following this.

brew install grep
==> Caveats
All commands have been installed with the prefix "g".
If you need to use these commands with their normal names, you
can add a "gnubin" directory to your PATH from your bashrc like:
  PATH="/usr/local/opt/grep/libexec/gnubin:$PATH"

Call with gsed instead of sed. The mac default doesn't like how grep -rl displays file names with the ./ preprended.

~/my-dir/configs$ grep -rl Promise . | xargs sed -i 's/Promise/Bluebird/g'

sed: 1: "./test_config.js": invalid command code .

I also had to use xargs -I{} sed -i 's/Promise/Bluebird/g' {} for files with a space in the name.

Clark Benham
  • 69
  • 1
  • 4
1

Very good examples. I had the challenge to edit in place many files and the -i option seems to be the only reasonable solution using it within the find command. Here the script to add "version:" in front of the first line of each file:

find . -name pkg.json -print -exec sed -i '.bak' '1 s/^/version /' {} \;
Robert
  • 133
  • 1
  • 3
1

In case you want to replace stings contain '/',you can use '?'. i.e. replace '/usr/local/bin/python' with '/usr/bin/python3' for all *.py files.

find . -name \*.py -exec sed -i 's?/usr/local/bin/python?/usr/bin/python3?g' {} \;

Mark Yuan
  • 21
  • 3