11

I have a requirement in my project to replace some existing text in a file like foo with some other text like fooofoo:

abc.txt
name
foo
foo1

So I tried:

sed -i "s/foo/fooofoo/g" abc.txt

However I get this error:

sed: illegal option -- i

I found in the manual that I have to use:

sed -i\ "s/foo/fooofoo/g" abc.txt

However this is not working either.

I found alternatives in perl and awk also but a solution in Solaris sed would be much appreciated.

I am using this version of bash:

GNU bash, version 3.2.57(1)-release (sparc-sun-solaris2.10)

  • On Solaris 11 and later, simply use /usr/gnu/bin/sed to get -i support. – alanc Oct 16 '16 at 15:40
15

Use ed. It's available on most platforms and it can edit your files in-place.
Since sed is based on ed the syntax for replacing patterns is similar:

ed -s infile <<\IN
,s/old/new/g
w
q
IN
  • Why ed instead of ex, just curious? (I know both are POSIX compliant and I've linked to the specs. ;) ) – Wildcard Oct 14 '16 at 20:23
  • 1
    @Wildcard - I could ask the opposite - why ex ? To answer your question: since I never use ex I'm not familiar with it. btw, I've seen setups where ed was present but ex was not (but not the opposite)... the most notable being my laptop :) – don_crissti Oct 14 '16 at 20:35
  • Good answer. :) My answer: Since I use Vim all the time, I am very familiar with ex commands. All the vi commands you can type that start with a colon are ex commands. There are of course a few Vim-specific extensions, but just the core set of commands is incredibly powerful and flexible and is plenty for scripted edits. – Wildcard Oct 14 '16 at 20:41
8

If you cannot install GNU sed, use:

sed "s/foo/fooofoo/g" abc.txt >abc.tmp && mv abc.tmp abc.txt

This uses redirection to send the output of sed to a temporary file. If sed completes successfully, then this overwrites abc.txt with the temporary file.

As can be seen from the source code for GNU sed, this is exactly what sed -i does. So, this is just about as efficient as sed -i.

If there is a chance that abc.tmp already exists, then you may want to use mktemp or a similar utility to generate the unique name for the temporary.

  • Thanks for help. However Is there any option in solaris by which we can update the existing file without creating a temp file? – tpsaitwal Oct 14 '16 at 8:28
  • 10
    Hate to break it to you, but even sed creates a temporary file. git.savannah.gnu.org/cgit/sed.git/tree/sed/sed.c#n84 – Jeff Schaller Oct 14 '16 at 10:39
  • 1
    You can do it if you don't care about hard links - see my example. There's still a temporary file, but it's hidden (unnamed). Without a temporary, you'd need to use ed, since it reads the whole file into memory. – Toby Speight Oct 14 '16 at 13:32
3

If you want the equivalent of sed -i.bak, it's pretty simple.

Consider this script for GNU sed:

#!/bin/sh

# Create an input file to demonstrate
trap 'rm -r "$dir"' EXIT
dir=$(mktemp -d)
grep -v '[[:upper:][:punct:]]' /usr/share/dict/words | head >"$dir/foo"

# sed program - removes 'aardvark' and 'aardvarks'
script='/aard/d'

##########
# What we want to do
sed -i.bak -e "$script" "$dir"
##########

# Prove that it worked
ls "$dir"
cat "$dir/foo"

We can simply replace the marked line with

cp "$dir/foo" "$dir/foo.bak" && sed -e "$script" "$dir/foo.bak" >"$dir/foo"

This moves the existing file to be a backup, and writes a new file.

If we want the equivalent of

sed -i -e "$script" "$dir"  # no backup

then it's slightly more complex. We can open the file for reading as standard input, then unlink it, before directing sed's output to replace it:

( cp "$dir/foo" "$dir/foo.bak"; exec <"$dir/foo.bak"; rm "$dir/foo.bak"; exec sed -e "$script" >"$dir/foo" )

We do this in a sub-shell, so that our original stdin is still available after this. It's possible to switch inputs and switch back without a subshell, but this way seems clearer to me.

Note that we're careful to copy first, rather than creating a new foo file - this is important if the file is known by more than one name (i.e. has hard links) and you want to be sure that you don't break the links.

3

First, you should know that the i\ command you're referring to is for inserting a line of text—not for saving the edited text back to the file. There is no POSIX-specified way to use sed for that.

What you can do is use ex, which is specified by POSIX:

printf '%s\n' '%s/find/replace/g' 'x' | ex file.txt

The x command is for "save and exit." You can also use wq but x is shorter.

The % sign at the start of the substitute command means, "Apply this command to every line in the buffer." You could use 1,$s/find/replace/g also.


One major difference between ex and sed is that sed is a stream editor. It only operates sequentially, line by line. ex is far more flexible than that, and in fact you can do interactive text file editing directly in ex. It is the immediate predecessor to vi.

1

Using sed and no visible temporary file:

You can avoid creating a separate visible "temp file":

exec 3<abc.txt
rm abc.txt
sed 's/foo/fooofoo/' <&3 >abc.txt
exec 3<&-

Explanation

Unix-like systems don't actually remove the file's contents from the disk until it's both unlinked in the filesystem, and not open in any process. So you can do exec 3< to open the file in the shell for reading on file descriptor 3, rm the file (which unlinks it from the file system), and then call sed with file descriptor 3 used as its input.

Note that this is very different from this:

# Does not work.
sed 's/foo/fooofoo/' <abc.txt >abc.txt

The difference is that when you do it in one command, the shell just opens the same file for both reading, and for writing with the option to truncate the file - since it's still the same file, you lose the contents. But if you open it for reading, then rm it, then open the same pathname for writing, you're actually creating a new file at the same pathname (but at a new inode and disk location, since the original is still open): so the contents are still available.

Then once you're done, you can close the file descriptor you opened previously (that's what the exec 3<&- special syntax does), which releases the original file so the operating system can delete (mark as unused) its disk space.

Caveats

There's a few things to keep in mind about this solution:.

  1. You only get one "go" through the contents - there's no portable way for a shell to "seek" back in the file descriptor - so once a program reads some of the contents, other programs will only see the remainder of the file. And sed will read the entire file.

  2. There's a small chance of your original file being lost if your shell/script/sed gets killed before it's done.

  • To be clear, @TobySpeight already posted an example of this solution at the end of his answer, but I thought it could use a more in-depth elaboration. – mtraceur Oct 15 '16 at 2:51
  • To the downvoter: While I'm sure just leaving a downvote makes you feel good about yourself, I'd appreciate if you'd contribute more by providing feedback as to what issues you have with this answer, how it can be improved, etc. – mtraceur Oct 18 '16 at 4:18
  • maybe you can use perl -i – c4f4t0r Oct 21 '16 at 8:53
  • @c4f4t0r: Sorry for the late reply: Yes, perl -i is another good option. The question specifically requested a solution compatible with Solaris' sed, in contrast to solutions with perl and awk that they mentioned already finding. Plus, my answer was mainly intended to add something that I thought was useful to know and missing from any of the other answers. – mtraceur Nov 11 '16 at 8:48

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.