18

I am using sed in a script to do a replace and I want to have the replaced file overwrite the file. Normally I think that you would use this:

% sed -i 's/cat/dog/' manipulate
sed: illegal option -- i

However as you can see my sed does not have that command.

I tried this:

% sed 's/cat/dog/' manipulate > manipulate

But this just turns manipulate into an empty file (makes sense).

This works:

% sed 's/cat/dog/' manipulate > tmp; mv tmp manipulate

But I was wondering if there was a standard way to redirect output into the same file that input was taken from.

sixtyfootersdude
  • 23,394
  • 39
  • 132
  • 200

10 Answers10

38

I commonly use the 3rd way, but with an important change:

$ sed 's/cat/dog/' manipulate > tmp && mv tmp manipulate

I.e. change ; to && so the move only happens if sed is successful; otherwise you'll lose your original file as soon as you make a typo in your sed syntax.

Note! For those reading the title and missing the OP's constraint "my sed doesn't support -i": For most people, sed will support -i, so the best way to do this is:

$ sed -i 's/cat/dog/' manipulate

Nathan Kidd
  • 2,799
  • 18
  • 22
8

Yes, -i is also supported in FreeBSD/MacOSX sed, but needs the empty string as an argument to edit a file in-place.

sed -i "" 's/old/new/g' file   # FreeBSD sed
gatt
  • 81
  • 1
  • 1
6

If you don't want to move copies around, you could use ed:

ed file.txt <<EOF
%s/cat/dog/
wq
EOF
amertune
  • 630
  • 5
  • 9
  • does ed support all of the regular expressions that sed does? Specifically ranges? – sixtyfootersdude Apr 07 '10 at 19:46
  • ed supports all of the regular expressions that sed does, including ranges. ed - works on files sed - works on streams The other main difference is that an ed command, by default will only work on the current line of a file (which is why I edited my example and added the %, to make the command run on all lines), while sed commands, by default, run against all lines. – amertune Apr 08 '10 at 15:38
  • you can also use "ex", which is alias to "vim". It's very feature-rich. – romaninsh Nov 06 '12 at 12:12
3

Kernighan and Pike in The Art of Unix Programming discuss this issue. Their solution is to write a script called overwrite, which allows one to do such things.

The usage is: overwrite file cmd file.

# overwrite: copy standard input to output after EOF

opath=$PATH
PATH=/bin:/usr/bin

case $# in
0|1)   echo 'Usage: overwrite file cmd [args]' 1>&2; exit 2
esac

file=$1; shift
new=/tmp/overwr1.$$; old=/tmp/overwr2.$$
trap 'rm -f $new $old; exit 1' 1 2 15  # clean up

if PATH=$opath "$@" >$new
then
       cp $file $old           # save original
       trap '' 1 2 15          # wr are commmitted
       cp $new $file
else
       echo "overwrite: $1 failed, $file unchanged" 1>&2
       exit 1
fi
rm -f $new $old

Once you have the above script in your $PATH, you can do:

overwrite manipulate sed 's/cat/dog/' manipulate

To make your life easier, you can use replace script from the same book:

# replace: replace  str1 in files with str2 in place
PATH=/bin:/usr/bin

case $# in
    0|2) echo 'Usage: replace str1 str2 files' 1>&2; exit 1
esac

left="$1"; right="$2"; shift; shift

for i
do
    overwrite $i sed "s@$left@$right@g" $i
done

Having replace in your $PATH too will allow you to say:

replace cat dog manipulate
Alok Singhal
  • 82,703
  • 18
  • 122
  • 153
  • 1
    Or in this case simply download a new version of sed, compliant with -i option, from www.sunfreeware.com ;) – Anders Apr 06 '10 at 14:52
  • +1, the overwrite script is very useful (after a few changes according to personal preferences)! – Arkku Apr 06 '10 at 19:34
  • @Anders: Replacing the system sed with a non-standard alternative, however free it may be, may not be a viable option, nor does it answer the question of how to do it with available/standard tools. – Arkku Apr 06 '10 at 19:35
  • The POSIX standards - which Solaris follows - only dictates a minimum set of option for sed to be compliant, so GNU/Sed or whatever you like to call is actually compliant with what you call "standard tools". – Anders Apr 06 '10 at 19:44
  • @Anders, It is compliant with POSIX, but if you use a non-POSIX option, then of course you are not relying on *standard* tools. One can of course install GNU sed on a system, but then one might not be able to. – Alok Singhal Apr 06 '10 at 23:25
2

You can use sponge from the moreutils.

sed "s/cat/dog/" manipulate | sponge manipulate
nh2
  • 22,055
  • 11
  • 64
  • 113
1

To change multiple files (and saving a backup of each as *.bak):

perl -p -i -e "s/oldtext/newtext/g" *

replaces any occurence of oldtext by newtext in all files in the current folder. However you will have to escape all perl special characters within oldtext and newtext using the backslash 

This is called a “Perl pie” (mnemonic: easy as a pie)
The -i flag tells it do do in-place replacement, and it should be ok to use single (“'”) as well as double (“””) quotes.

    If using ./* instead of just *, you should be able to do it in all sub-directories 
See man perlrun for more details, including how to take a backup file of the original.
using sed:
            sed -i    's/old/new/g' ./*  (used in GNU)
    sed -i '' 's/old/new/g' ./*  (used in FreeBSD)
Stenemo
  • 567
  • 6
  • 12
1

Perhaps -i is gnu sed, or just an old version of sed, but anyways. You're on the right track. The first option is probably the most common one, the third option is if you want it to work everywhere (including solaris machines)... :) These are the 'standard' ways of doing it.

falstro
  • 31,759
  • 8
  • 68
  • 85
0

-i option is not available in standard sed.

Your alternatives are your third way or perl.

mouviciel
  • 62,742
  • 10
  • 106
  • 135
-1

A lot of answers, but none of them is correct. Here is the correct and simplest one:

$ echo "111 222 333" > file.txt
$ sed -i -s s/222/444/ file.txt 
$ cat file.txt
111 444 333
$ 
Vladimir Prudnikov
  • 5,908
  • 4
  • 39
  • 50
  • 1
    The OP clearly stated that the `-i` flag is considered "illegal" by his version of `sed`. So it cannot be used... – arkascha Jul 03 '15 at 09:19
-2

Workaround using open file handles:

exec 3<manipulate 

Prevent open file from being truncated:

rm manipulate
sed 's/cat/dog/' <&3 > manipulate
Jürgen Hötzel
  • 16,398
  • 3
  • 38
  • 55