How to use sed to replace only the first occurrence in a file?

Viewed 456866

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.

24 Answers
 # 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

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

#!/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.

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|$)//.

Just add the number of occurrence at the end:

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

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

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.

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/'

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.

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.

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()
sed -e 's/pattern/REPLACEMENT/1' <INPUTFILE
Related