views:

9349

answers:

8

I want to update a large number of C++ source files with an extra include directive before any existing #includes. For this sort of task I normally use a small bash script with sed to re-write the file.

How do I get sed to replace just the first occurrence of a string in a file rather than replacing the every occurrence?

If I use

sed s/#include/#include "newfile.h"\n#include/

it replaces all #includes.

Alternative suggestions to achieve the same thing are also welcome.

+5  A: 
     # sed script to change "foo" to "bar" only on the first occurrence
     1{x;s/^/first/;x;}
     1,/foo/{x;/first/s///;x;s/foo/bar/;}
     #---end of script---

or if you prefer

sed '0,/RE/s//to_that/' file

Source

Ben Hoffstein
that's an awful lot of swapping hold and pattern spaces...
mitchnull
I think I prefer the 'or if you prefer' solution. It would also be good to explain the answers - and to make the answer address the question directly, and then generalize, rather than generalize only. But good answer.
Jonathan Leffler
Thanks for the tips Jonathan. Feel free to add your own answer.
Ben Hoffstein
+7  A: 

You could use awk to do something similar..

awk '/#include/ && !done { print "#include \"newfile.h\""; done=1;}; 1;' file.c

Explanation:

/#include/ && !done

Runs the action statement between {} when the line matches "#include" and we haven't already processed it.

{print "#include \"newfile.h\""; done=1;}

This prints #include "newfile.h", we need to escape the quotes. Then we set the done variable to 1, so we don't add more includes.

1;

This means "print out the line" - an empty action defaults to print $0, which prints out the whole line. A one liner and easier to understand than sed IMO :-)

rq
+1  A: 

Just add the number of occurrence at the end:

sed s/#include/#include "newfile.h"\n#include/1
unexist
Unfortunately, this does not work. It replaces the just first occurrence on each line of the file and not the first occurrence in the file.
David Dibben
Additionally, it is a GNU sed extension, not a standard sed feature.
Jonathan Leffler
This answer helped in my case - thanks
tttppp
+2  A: 

A possible solution:

    /#include/!{p;d;}
    i\
    #include "newfile.h"
    :
    n
    b

Explanation:

  • read lines until we find the #include, print these lines then start new cycle
  • insert the new include line
  • enter a loop that just reads lines (by default sed will also print these lines), we won't get back to the first part of the script from here
mitchnull
+4  A: 
#!/bin/sed -f
1,/^#include/ {
    /^#include/i\
#include "newfile.h"
}

How this script works: For lines between 1 and the first #include (after line 1), if the line starts with #include, then prepend the specified line.

However, if the first #include is in line 1, then both line 1 and the next subsequent #include will have the line prepended. If you are using GNU sed, it has an extension where 0,/^#include/ (instead of 1,) will do the right thing.

Chris Jester-Young
A: 

sed is all about editing lines of text, not files.

I would use another tool like awk/Perl.

duffbeer703
And what, pray tell, is a text file but a series of lines of text?
Jonathan Leffler
Obviously a text file is a stream of characters delimited by newlines. I just think that awk is a better tool for the type of problem the person posting the question asked.
duffbeer703
+1  A: 

i would do this with an awk script:

BEGIN {i=0}
(i==0) && /#include/ {print "#include \"newfile.h\""; i=1}
{print $0}    
END {}

then run it with awk:

awk -f awkscript headerfile.h > headerfilenew.h

might be sloppy, I'm new to this.

wakingrufus
A: 

sed '0,/pattern/s/pattern/replacement/' filename this worked for me.

example sed '0,//s//Sub menu<\/Menu>/' try.txt > abc.txt

Sushil