How to write a `sed` script that replaces the text enclosed between two markers with the contents of another file

Viewed 60

I'm trying to write a sed script that replaces the text enclosed between two markers with the contents of another file.

Say I have the following file with the markers <!-- Start --> and <!-- End -->:

# index.html
...
<!-- Start -->
<p>Old content</p>
<!-- End -->
...

And I want to replace the enclosed text with the content of this file:

# snippet.html
<p>New content</p>

I tried this sed script:

$ cat snippet.html | sed -i -e '/<\!-- Start -->/,/<\!-- End -->/ { r /dev/stdin' -e';d};' index.html

It replaces both the content and the markers, but I need to keep the <!-- Start --> and <!-- End --> markers so I can replace the content again in the future.

What changes should I make to the sed script?

Thanks in advance!

3 Answers

All that you need to do to your code is add //! before the d:

cat snippet | sed -e '/<\!-- Start -->/,/<\!-- End -->/ { r /dev/stdin' -e '; //!d }' file

// is shorthand for "last match" ... so in the case of /START/,/END/{ } without any other match within { }, it will match both /START/ and /END/.

//!d ensures that the range, excepting the matching lines at the start and end, will be deleted.

ETA: as @potong raised in the comments, the use of r /dev/stdin seems to be a special case-- only the first r will add anything to the output stream.

This might work for you (GNU sed):

sed -e '/<!-- Start -->/{n;r snippetFile' -e ':a;N;/<!-- End -->/D;s/\n//;ba}' file 

Print the start marker and then append the snippet file.

Gather up lines until an end marker, removing their newlines and delete them using the D command, leaving the end marker as is.

N.B. The solution is in two parts as the r command demands a newline as a terminator (a separate command set has the same effect as a newline).

The above solution does not cater for empty markers for which a dummy line must be inserted:

 sed -e '/<!-- Start -->/{n;r snippetFile ' -e 's/^/\n/;:a;N;/<!-- End -->/D;s/\n//g;ba}' file

An alternative solution is to insert and append the markers to the snippet file:

sed -z 's/^/<!-- Start -->\n/;s/.*/&\\/mg;s/$/<!-- End -->/' snippetFile |
sed '/<!-- Start -->/,/<!-- End -->/c'"$(cat)" file

N.B. In this solution, the c command expects all its line but the last, to be terminated by \.

Assuming the markers are alone on their lines:

cat snippet.html | sed -i -n -e '
    /<\!-- Start -->/r /dev/stdin
    1,//{;p;d;}
    /<\!-- End -->/,$p
' index.html
  • // repeats the previous regex search

The code above fails if first marker is on first line of file. In that case, with GNU sed 0,// can be used instead of 1,//. Alternatively, this version should work correctly:

cat snippet.html | sed -i -n -e '
    /<\!-- $Start -->/{
        p
        r /dev/stdin
        :d
        n
        /<\!-- End -->/!bd
    }
    p
' index.html
Related