323

I would like 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 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.

kvantour
  • 25,269
  • 4
  • 47
  • 72
David Dibben
  • 18,460
  • 6
  • 41
  • 41

25 Answers25

389

A sed script that will only replace the first occurrence of "Apple" by "Banana"

Example

     Input:      Output:

     Apple       Banana
     Apple       Apple
     Orange      Orange
     Apple       Apple

This is the simple script: Editor's note: works with GNU sed only.

sed '0,/Apple/{s/Apple/Banana/}' input_filename

The first two parameters 0 and /Apple/ are the range specifier. The s/Apple/Banana/ is what is executed within that range. So in this case "within the range of the beginning (0) up to the first instance of Apple, replace Apple with Banana. Only the first Apple will be replaced.

Background: In traditional sed the range specifier is also "begin here" and "end here" (inclusive). However the lowest "begin" is the first line (line 1), and if the "end here" is a regex, then it is only attempted to match against on the next line after "begin", so the earliest possible end is line 2. So since range is inclusive, smallest possible range is "2 lines" and smallest starting range is both lines 1 and 2 (i.e. if there's an occurrence on line 1, occurrences on line 2 will also be changed, not desired in this case). GNU sed adds its own extension of allowing specifying start as the "pseudo" line 0 so that the end of the range can be line 1, allowing it a range of "only the first line" if the regex matches the first line.

Or a simplified version (an empty RE like // means to re-use the one specified before it, so this is equivalent):

sed '0,/Apple/{s//Banana/}' input_filename

And the curly braces are optional for the s command, so this is also equivalent:

sed '0,/Apple/s//Banana/' input_filename

All of these work on GNU sed only.

You can also install GNU sed on OS X using homebrew brew install gnu-sed.

rogerdpack
  • 62,887
  • 36
  • 269
  • 388
tim
  • 3,899
  • 2
  • 13
  • 2
  • 205
    translated into human language: start at line 0, continue until you match 'Apple', execute the substitution in curly brackets. cfr: http://www.grymoire.com/Unix/Sed.html#uh-29 – mariotomo Jun 25 '13 at 07:46
  • 10
    On OS X, I get `sed: 1: "…": bad flag in substitute command: '}'` – ELLIOTTCABLE Jan 07 '15 at 16:57
  • 5
    @ELLIOTTCABLE on OS X, use `sed -e '1s/Apple/Banana/;t' -e '1,/Apple/s//Banana/'`. From @MikhailVS's answer (currently) way down below. – djb Apr 28 '15 at 00:20
  • 8
    Works without the brackets too: `sed '0,/foo/s/foo/bar/'` – Innokenty Jun 04 '15 at 14:00
  • 7
    I get `sed: -e expression #1, char 3: unexpected `,'` with this – Jonathan Mar 24 '17 at 12:53
  • Solved by using ```sed -i '|DATADIR.*=.*|{s/DATADIR.*=.*|DATADIR=/var/lib/`hostname`|1}``` Note 1 at end after last delimiter – Jonathan Mar 24 '17 at 13:05
  • 2
    @Jonathan Can you rephrase your answer in the universally-accepted Apple-Banana parlance? I think I'm getting the same problem because my "banana" happens to include a bash variable: `sed -i -E "0,|(Apple).*|{s|(Apple:).*|Banana: "$BananaVariable"/data.txt|}" filename` – so860 May 29 '18 at 19:33
  • 1
    Shout out for the people whom actually provide quick, clean and simple examples. – Amin NAIRI Jun 08 '18 at 07:34
  • Shout out for the people whom actually provide quick, clean and simple examples. – Amin NAIRI Jun 08 '18 at 07:34
  • This doesn't seem to work if the address is not the same as the pattern you want to replace. For example, `sed '0,/White/ s/cat/dog/' filename` replaces all instances of "cat" with "dog" up to and including the first line with "White cat". You would need to do something more like `sed '0,/White/ s/White cat/White dog/' filename` – Andrew Schlei Jan 21 '19 at 22:17
  • You don't have to repeat Apple: `sed '0,/Apple/s//Banana/'`. Not repeating is essential for complex patterns, e.g., I used this to work to update the first version property: `sed -i -r -e '0,/^(\s* – haridsv May 24 '19 at 11:30
  • In OSX you can also use `gsed`, which implements the GNU `sed`. – lucastamoios Jan 13 '20 at 16:27
  • Is it possible to *delete* (rather than substitute) only the first matching line? If I try `sed '0,/Orange/d' input_filename` I seem to lose all lines upto and including the (first?) matching line. – AstroFloyd Jul 08 '20 at 14:15
181
 # 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: Editor's note: works with GNU sed only.

sed '0,/foo/s//bar/' file 

Source

tedder42
  • 23,519
  • 13
  • 86
  • 102
Ben Hoffstein
  • 102,129
  • 8
  • 104
  • 120
  • 110
    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 Sep 29 '08 at 13:15
  • 11
    FYI for Mac users, you have to replace the 0 with a 1, so: sed '1,/RE/s//to_that/' file – mhost Oct 05 '14 at 01:24
  • 1
    @mhost No, it will than replace if pattern is found in line #1 and not if pattern is at other line, but still is the first found pattern. PS it should be mention that this `0,` only works with `gnu sed` – Jotne Oct 29 '14 at 08:59
  • 14
    Could somebody please explain the 'or if you prefeer' solution? I don't know where to put the "from" pattern. – Jean-Luc Nacif Coelho Feb 02 '15 at 18:44
  • 4
    @Jean-LucNacifCoelho: using `s//` - i.e., an _empty_ regex - means that the most recently applied regex is implicitly reused; in this case, `RE`. This convenient shortcut means you don't have to duplicate the range-ending regex in your `s` call. – mklement0 Oct 29 '15 at 06:21
  • 4
    Full example: `echo "abc\nabc" | sed '0,/^abc/s//ab/'` – Christoph Jun 13 '16 at 13:05
  • It would make sense to quit after the (first and only) substitution is done : `sed '0,/RE/{s//to_that/;q;}' file` – Nicolas Melay May 02 '17 at 11:17
  • 1
    Why can't I change the default separator? `/`. when I try to use `:` instead of `/`, I got an error. And what is that `0` at first? – Mohammed Noureldin Aug 28 '17 at 01:11
  • @Nico57 no, that would quit on line 0, but this would work: `/RE/{s//to_that/;q}` – Aryeh Leib Taurog Nov 20 '17 at 13:23
  • 1
    @AryehLeibTaurog It won't work either, file will be truncated after match. The whole idea of using "q" was flawed, sorry about that. – Nicolas Melay Dec 19 '17 at 00:15
  • @Nico57 ah, yes. I had a slightly different use case in mind. – Aryeh Leib Taurog Dec 19 '17 at 06:28
  • 1
    Anybody get that first example to actually work? For me it doesn't work with GNU sed or BSD sed... – rogerdpack Jan 03 '20 at 22:20
  • How to escape bracket? – pete May 31 '21 at 02:18
73

An overview of the many helpful existing answers, complemented with explanations:

The examples here use a simplified use case: replace the word 'foo' with 'bar' in the first matching line only.
Due to use of ANSI C-quoted strings ($'...') to provide the sample input lines, bash, ksh, or zsh is assumed as the shell.


GNU sed only:

Ben Hoffstein's anwswer shows us that GNU provides an extension to the POSIX specification for sed that allows the following 2-address form: 0,/re/ (re represents an arbitrary regular expression here).

0,/re/ allows the regex to match on the very first line also. In other words: such an address will create a range from the 1st line up to and including the line that matches re - whether re occurs on the 1st line or on any subsequent line.

  • Contrast this with the POSIX-compliant form 1,/re/, which creates a range that matches from the 1st line up to and including the line that matches re on subsequent lines; in other words: this will not detect the first occurrence of an re match if it happens to occur on the 1st line and also prevents the use of shorthand // for reuse of the most recently used regex (see next point).1

If you combine a 0,/re/ address with an s/.../.../ (substitution) call that uses the same regular expression, your command will effectively only perform the substitution on the first line that matches re.
sed provides a convenient shortcut for reusing the most recently applied regular expression: an empty delimiter pair, //.

$ sed '0,/foo/ s//bar/' <<<$'1st foo\nUnrelated\n2nd foo\n3rd foo' 
1st bar         # only 1st match of 'foo' replaced
Unrelated
2nd foo
3rd foo

A POSIX-features-only sed such as BSD (macOS) sed (will also work with GNU sed):

Since 0,/re/ cannot be used and the form 1,/re/ will not detect re if it happens to occur on the very first line (see above), special handling for the 1st line is required.

MikhailVS's answer mentions the technique, put into a concrete example here:

$ sed -e '1 s/foo/bar/; t' -e '1,// s//bar/' <<<$'1st foo\nUnrelated\n2nd foo\n3rd foo'
1st bar         # only 1st match of 'foo' replaced
Unrelated
2nd foo
3rd foo

Note:

  • The empty regex // shortcut is employed twice here: once for the endpoint of the range, and once in the s call; in both cases, regex foo is implicitly reused, allowing us not to have to duplicate it, which makes both for shorter and more maintainable code.

  • POSIX sed needs actual newlines after certain functions, such as after the name of a label or even its omission, as is the case with t here; strategically splitting the script into multiple -e options is an alternative to using an actual newlines: end each -e script chunk where a newline would normally need to go.

1 s/foo/bar/ replaces foo on the 1st line only, if found there. If so, t branches to the end of the script (skips remaining commands on the line). (The t function branches to a label only if the most recent s call performed an actual substitution; in the absence of a label, as is the case here, the end of the script is branched to).

When that happens, range address 1,//, which normally finds the first occurrence starting from line 2, will not match, and the range will not be processed, because the address is evaluated when the current line is already 2.

Conversely, if there's no match on the 1st line, 1,// will be entered, and will find the true first match.

The net effect is the same as with GNU sed's 0,/re/: only the first occurrence is replaced, whether it occurs on the 1st line or any other.


NON-range approaches

potong's answer demonstrates loop techniques that bypass the need for a range; since he uses GNU sed syntax, here are the POSIX-compliant equivalents:

Loop technique 1: On first match, perform the substitution, then enter a loop that simply prints the remaining lines as-is:

$ sed -e '/foo/ {s//bar/; ' -e ':a' -e '$!{n;ba' -e '};}' <<<$'1st foo\nUnrelated\n2nd foo\n3rd foo'
1st bar
Unrelated
2nd foo
3rd foo

Loop technique 2, for smallish files only: read the entire input into memory, then perform a single substitution on it.

$ sed -e ':a' -e '$!{N;ba' -e '}; s/foo/bar/' <<<$'1st foo\nUnrelated\n2nd foo\n3rd foo'
1st bar
Unrelated
2nd foo
3rd foo

1 1.61803 provides examples of what happens with 1,/re/, with and without a subsequent s//:

  • sed '1,/foo/ s/foo/bar/' <<<$'1foo\n2foo' yields $'1bar\n2bar'; i.e., both lines were updated, because line number 1 matches the 1st line, and regex /foo/ - the end of the range - is then only looked for starting on the next line. Therefore, both lines are selected in this case, and the s/foo/bar/ substitution is performed on both of them.
  • sed '1,/foo/ s//bar/' <<<$'1foo\n2foo\n3foo' fails: with sed: first RE may not be empty (BSD/macOS) and sed: -e expression #1, char 0: no previous regular expression (GNU), because, at the time the 1st line is being processed (due to line number 1 starting the range), no regex has been applied yet, so // doesn't refer to anything.
    With the exception of GNU sed's special 0,/re/ syntax, any range that starts with a line number effectively precludes use of //.
HoldOffHunger
  • 18,769
  • 10
  • 104
  • 133
mklement0
  • 382,024
  • 64
  • 607
  • 775
  • For the POSIX solution, what if the first occurrence is not on the first line? e.g. `some lines\n1st foo\nsome text or 2nd foo\nmore foo` – jimmymcheung Jun 09 '23 at 15:57
  • @jimmymcheung, seems to me it works just fine; e.g.: `sed -e '1 s/foo/bar/; t' -e '1,// s//bar/' <<<$'Unrelated 1\n1st foo\nUnrelated 2\n2nd foo\n3rd foo'` – mklement0 Jun 09 '23 at 16:04
66
sed '0,/pattern/s/pattern/replacement/' filename

this worked for me.

example

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

Editor's note: both work with GNU sed only.

rogerdpack
  • 62,887
  • 36
  • 269
  • 388
Sushil
  • 669
  • 5
  • 2
  • 2
    @Landys this still replaces instances in the other lines too; not just the first instance – sarat Apr 11 '15 at 03:56
  • 1
    @sarat Yes, you're right. `sed '1,/pattern/s/pattern/replacement/' filename` only works if "the pattern will not occur on the first line" on Mac. I'll delete my previous comment since it's not accurate. The detail can be found here (http://www.linuxtopia.org/online_books/linux_tool_guides/the_sed_faq/sedfaq4_004.html). Andy's answer only works for GNU sed, but not the one on Mac. – Landys Apr 11 '15 at 13:53
  • The simplest answer! Congrats. – Paulo Jul 07 '20 at 22:53
33

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 :-)

richq
  • 55,548
  • 20
  • 150
  • 144
  • 4
    This answer is more portable than the sed solutions, which rely on gnu sed etc.. (e.g. sed in OS-X sucks!) – Jay Taylor Nov 23 '11 at 19:09
  • 1
    This is truly more understandable, but for me it adds a line instead of replacing it; command used: `awk '/version/ && !done {print " \"version\": \"'${NEWVERSION}'\""; done=1;}; 1;' package.json` – Cereal Killer Aug 07 '15 at 13:58
  • same here, most understandable command, but it adds a line above the found string instead of replacing it – Flion Nov 01 '18 at 14:46
  • 1
    Answer is quite readable. Here's my version that replaces string instead of adding a new line. `awk '/#include/ && !done { gsub(/#include/, "include \"newfile.h\""); done=1}; 1' file.c` – Orsiris de Jong May 21 '19 at 11:08
28

Quite a comprehensive collection of answers on linuxtopia sed FAQ. It also highlights that some answers people provided won't work with non-GNU version of sed, eg

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

in non-GNU version will have to be

sed -e '1s/RE/to_that/;t' -e '1,/RE/s//to_that/'

However, this version won't work with gnu sed.

Here's a version that works with both:

-e '/RE/{s//to_that/;:a' -e '$!N;$!ba' -e '}'

ex:

sed -e '/Apple/{s//Banana/;:a' -e '$!N;$!ba' -e '}' filename
rogerdpack
  • 62,887
  • 36
  • 269
  • 388
MikhailVS
  • 453
  • 5
  • 4
  • Indeed, tested working on Ubuntu Linux v16 and FreeBSD v10.2 . Thanks you. – Sopalajo de Arrierez Jun 10 '19 at 21:39
  • FYI: POSIX (non-GNU) sed officially has a default buffer of 8K. So the second option, of reading all the lines into the buffer, then changing the first instance in the buffer, may blow up if the file is over 8K – arober11 Jul 26 '23 at 14:35
14

With GNU sed's -z option you could process the whole file as if it was only one line. That way a s/…/…/ would only replace the first match in the whole file. Remember: s/…/…/ only replaces the first match in each line, but with the -z option sed treats the whole file as a single line.

sed -z 's/#include/#include "newfile.h"\n#include'

In the general case you have to rewrite your sed expression since the pattern space now holds the whole file instead of just one line. Some examples:

  • s/text.*// can be rewritten as s/text[^\n]*//. [^\n] matches everything except the newline character. [^\n]* will match all symbols after text until a newline is reached.
  • s/^text// can be rewritten as s/(^|\n)text//.
  • s/text$// can be rewritten as s/text(\n|$)//.
Socowi
  • 25,550
  • 3
  • 32
  • 54
12
#!/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.

C. K. Young
  • 219,335
  • 46
  • 382
  • 435
10

Just add the number of occurrence at the end:

sed s/#include/#include "newfile.h"\n#include/1
unexist
  • 2,518
  • 23
  • 27
  • 8
    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 Sep 29 '08 at 12:32
  • 1
    Additionally, it is a GNU sed extension, not a standard sed feature. – Jonathan Leffler Sep 29 '08 at 13:19
  • 10
    Hmmm...time passes. POSIX 2008/2013 for [`sed`](http://pubs.opengroup.org/onlinepubs/9699919799/utilities/sed.html) specifies the substitute command with: _`[2addr]s/BRE/replacement/flags`_ and notes that "The value of flags shall be zero or more of: _n_ Substitute for the nth occurrence only of the BRE found within the pattern space." Thus, at least in POSIX 2008, the trailing `1` is not a GNU `sed` extension. Indeed, even in the [SUS/POSIX 1997](http://pubs.opengroup.org/onlinepubs/7990989775/) standard, this was supported, so I was badly out of line back in 2008. – Jonathan Leffler Jan 17 '16 at 03:44
7

A possible solution:

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

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
  • 6,161
  • 2
  • 31
  • 23
6

I know this is an old post but I had a solution that I used to use:

grep -E -m 1 -n 'old' file | sed 's/:.*$//' - | sed 's/$/s\/old\/new\//' - | sed -f - file

Basically use grep to print the first occurrence and stop there. Additionally print line number ie 5:line. Pipe that into sed and remove the : and anything after so you are just left with a line number. Pipe that into sed which adds s/.*/replace to the end number, which results in a 1 line script which is piped into the last sed to run as a script on the file.

so if regex = #include and replace = blah and the first occurrence grep finds is on line 5 then the data piped to the last sed would be 5s/.*/blah/.

Works even if first occurrence is on the first line.

rogerdpack
  • 62,887
  • 36
  • 269
  • 388
  • I utterly hate multiline sed scripts or sed commands with anything but s and a linenumber so I'm on board with this approach. Here is what I used for my usecase (works with bash): filepath=/etc/hosts ; patt='^\\(127\.0\.0\.1.*\\)' ; repl='\1 newhostalias' ; sed $( IFS=: linearray=($(grep -E -m 1 -n "$patt" "$filepath")) && echo ${linearray[0]})s/"$patt"/"$repl"/ "$filepath" – parity3 Dec 14 '15 at 21:32
  • It works. Though only with sed's that are smart enough to accept `sed -f -` which some aren't, but you can work around it :) – rogerdpack Jan 03 '20 at 22:33
2

Using FreeBSD ed and avoid ed's "no match" error in case there is no include statement in a file to be processed:

teststr='
#include <stdio.h>
#include <stdlib.h>
#include <inttypes.h>
'

# using FreeBSD ed
# to avoid ed's "no match" error, see
# *emphasized text*http://codesnippets.joyent.com/posts/show/11917 
cat <<-'EOF' | sed -e 's/^ *//' -e 's/ *$//' | ed -s <(echo "$teststr")
   H
   ,g/# *include/u\
   u\
   i\
   #include "newfile.h"\
   .
   ,p
   q
EOF
nazq
  • 21
  • 1
  • This is remarkably similar to [timo](http://stackoverflow.com/a/5818901/15168)'s [answer](https://stackoverflow.com/a/5818901/15168) but was added over a year later. – Jonathan Leffler Jan 17 '16 at 03:58
2

This might work for you (GNU sed):

sed -si '/#include/{s//& "newfile.h\n&/;:a;$!{n;ba}}' file1 file2 file....

or if memory is not a problem:

sed -si ':a;$!{N;ba};s/#include/& "newfile.h\n&/' file1 file2 file...
potong
  • 55,640
  • 6
  • 51
  • 83
2

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
  • 385
  • 1
  • 3
  • 10
2

If anyone came here to replace a character for the first occurrence in all lines (like myself), use this:

sed '/old/s/old/new/1' file

-bash-4.2$ cat file
123a456a789a
12a34a56
a12
-bash-4.2$ sed '/a/s/a/b/1' file
123b456a789a
12b34a56
b12

By changing 1 to 2 for example, you can replace all the second a's only instead.

FatihSarigol
  • 647
  • 7
  • 14
2

As an alternative suggestion you may want to look at the ed command.

man 1 ed

teststr='
#include <stdio.h>
#include <stdlib.h>
#include <inttypes.h>
'

# for in-place file editing use "ed -s file" and replace ",p" with "w"
# cf. http://wiki.bash-hackers.org/howto/edit-ed
cat <<-'EOF' | sed -e 's/^ *//' -e 's/ *$//' | ed -s <(echo "$teststr")
   H
   /# *include/i
   #include "newfile.h"
   .
   ,p
   q
EOF
timo
  • 21
  • 1
2

I finally got this to work in a Bash script used to insert a unique timestamp in each item in an RSS feed:

        sed "1,/====RSSpermalink====/s/====RSSpermalink====/${nowms}/" \
            production-feed2.xml.tmp2 > production-feed2.xml.tmp.$counter

It changes the first occurrence only.

${nowms} is the time in milliseconds set by a Perl script, $counter is a counter used for loop control within the script, \ allows the command to be continued on the next line.

The file is read in and stdout is redirected to a work file.

The way I understand it, 1,/====RSSpermalink====/ tells sed when to stop by setting a range limitation, and then s/====RSSpermalink====/${nowms}/ is the familiar sed command to replace the first string with the second.

In my case I put the command in double quotation marks becauase I am using it in a Bash script with variables.

Rob W
  • 341,306
  • 83
  • 791
  • 678
1

The use case can perhaps be that your occurences are spread throughout your file, but you know your only concern is in the first 10, 20 or 100 lines.

Then simply adressing those lines fixes the issue - even if the wording of the OP regards first only.

sed '1,10s/#include/#include "newfile.h"\n#include/'
sastorsl
  • 2,015
  • 1
  • 16
  • 17
1

I needed a solution that would work both on GNU and BSD, and I also knew that the first line would never be the one I'd need to update:

sed -e "1,/pattern/s/pattern/replacement/"

Trying the // feature to not repeat the pattern did not work for me, hence needing to repeat it.

Zebiano
  • 373
  • 5
  • 13
0

The following command removes the first occurrence of a string, within a file. It removes the empty line too. It is presented on an xml file, but it would work with any file.

Useful if you work with xml files and you want to remove a tag. In this example it removes the first occurrence of the "isTag" tag.

Command:

sed -e 0,/'<isTag>false<\/isTag>'/{s/'<isTag>false<\/isTag>'//}  -e 's/ *$//' -e  '/^$/d'  source.txt > output.txt

Source file (source.txt)

<xml>
    <testdata>
        <canUseUpdate>true</canUseUpdate>
        <isTag>false</isTag>
        <moduleLocations>
            <module>esa_jee6</module>
            <isTag>false</isTag>
        </moduleLocations>
        <node>
            <isTag>false</isTag>
        </node>
    </testdata>
</xml>

Result file (output.txt)

<xml>
    <testdata>
        <canUseUpdate>true</canUseUpdate>
        <moduleLocations>
            <module>esa_jee6</module>
            <isTag>false</isTag>
        </moduleLocations>
        <node>
            <isTag>false</isTag>
        </node>
    </testdata>
</xml>

ps: it didn't work for me on Solaris SunOS 5.10 (quite old), but it works on Linux 2.6, sed version 4.1.5

Andreas Panagiotidis
  • 2,763
  • 35
  • 32
  • This looks remarkably like the same basic idea as a number of previous answers, with the same caveat that it only works with GNU `sed` (hence it didn't work with Solaris). You should delete this, please — it really doesn't provide distinctive new information to a question that was already 4½ years old when you answered. Granted, it does have a worked example, but that's of debatable value when the question has as many answers as this one does. – Jonathan Leffler Jan 17 '16 at 03:56
0

Nothing new but perhaps a little more concrete answer: sed -rn '0,/foo(bar).*/ s%%\1%p'

Example: xwininfo -name unity-launcher produces output like:

xwininfo: Window id: 0x2200003 "unity-launcher"

  Absolute upper-left X:  -2980
  Absolute upper-left Y:  -198
  Relative upper-left X:  0
  Relative upper-left Y:  0
  Width: 2880
  Height: 98
  Depth: 24
  Visual: 0x21
  Visual Class: TrueColor
  Border width: 0
  Class: InputOutput
  Colormap: 0x20 (installed)
  Bit Gravity State: ForgetGravity
  Window Gravity State: NorthWestGravity
  Backing Store State: NotUseful
  Save Under State: no
  Map State: IsViewable
  Override Redirect State: no
  Corners:  +-2980+-198  -2980+-198  -2980-1900  +-2980-1900
  -geometry 2880x98+-2980+-198

Extracting window ID with xwininfo -name unity-launcher|sed -rn '0,/^xwininfo: Window id: (0x[0-9a-fA-F]+).*/ s%%\1%p' produces:

0x2200003
Stephen Niedzielski
  • 2,497
  • 1
  • 28
  • 34
0

POSIXly (also valid in sed), Only one regex used, need memory only for one line (as usual):

sed '/\(#include\).*/!b;//{h;s//\1 "newfile.h"/;G};:1;n;b1'

Explained:

sed '
/\(#include\).*/!b          # Only one regex used. On lines not matching
                            # the text  `#include` **yet**,
                            # branch to end, cause the default print. Re-start.
//{                         # On first line matching previous regex.
    h                       # hold the line.
    s//\1 "newfile.h"/      # append ` "newfile.h"` to the `#include` matched.
    G                       # append a newline.
  }                         # end of replacement.
:1                          # Once **one** replacement got done (the first match)
n                           # Loop continually reading a line each time
b1                          # and printing it by default.
'                           # end of sed script.
0

A possible solution here might be to tell the compiler to include the header without it being mentioned in the source files. IN GCC there are these options:

   -include file
       Process file as if "#include "file"" appeared as the first line of
       the primary source file.  However, the first directory searched for
       file is the preprocessor's working directory instead of the
       directory containing the main source file.  If not found there, it
       is searched for in the remainder of the "#include "..."" search
       chain as normal.

       If multiple -include options are given, the files are included in
       the order they appear on the command line.

   -imacros file
       Exactly like -include, except that any output produced by scanning
       file is thrown away.  Macros it defines remain defined.  This
       allows you to acquire all the macros from a header without also
       processing its declarations.

       All files specified by -imacros are processed before all files
       specified by -include.

Microsoft's compiler has the /FI (forced include) option.

This feature can be handy for some common header, like platform configuration. The Linux kernel's Makefile uses -include for this.

Kaz
  • 55,781
  • 9
  • 100
  • 149
-2

I will make a suggestion that is not exactly what the original question asks for, but for those who also want to specifically replace perhaps the second occurrence of a match, or any other specifically enumerated regular expression match. Use a python script, and a for loop, call it from a bash script if needed. Here's what it looked like for me, where I was replacing specific lines containing the string --project:

def replace_models(file_path, pixel_model, obj_model):
    # find your file --project matches
    pattern = re.compile(r'--project.*')
    new_file = ""
    with open(file_path, 'r') as f:
        match = 1
        for line in f:
            # Remove line ending before we do replacement
            line = line.strip()
            # replace first --project line match with pixel
            if match == 1:
                result = re.sub(pattern, "--project='" + pixel_model + "'", line)
            # replace second --project line match with object
            elif match == 2:
                result = re.sub(pattern, "--project='" + obj_model + "'", line)
            else:
                result = line
            # Check that a substitution was actually made
            if result is not line:
                # Add a backslash to the replaced line
                result += " \\"
                print("\nReplaced ", line, " with ", result)
                # Increment number of matches found
                match += 1
            # Add the potentially modified line to our new file
            new_file = new_file + result + "\n"
        # close file / save output
        f.close()
    fout = open(file_path, "w")
    fout.write(new_file)
    fout.close()
chaytan
  • 57
  • 1
  • 10
-3
sed -e 's/pattern/REPLACEMENT/1' <INPUTFILE
warhansen
  • 704
  • 1
  • 8
  • 22