Replace block of text inside a file using the contents of another file using sed

Viewed 833

I am looking to replace a block of text that is between markers with the contents of another file. I came across this solution but it only works with one line

$ sed -n '/foo/{p;:a;N;/bar/!ba;s/.*\n/REPLACEMENT\n/};p' file
line 1
line 2
foo
REPLACEMENT
bar
line 6
line 7

I am trying to get the following working but it's not.

content=`cat file_content` 
sed -n '/foo/{p;:a;N;/bar/!ba;s/.*\n/${content}\n/};p' file

output

line 1
line 2
foo
${content}
bar
line 6
line 7

How can I get ${content} to list the output of the file?

3 Answers

So I guess this should be a reasonably short way of doing it to replace text between foo and bar lines with content of file file_content:

sed -e '/^foo$/,/^bar$/{/^bar$/{x;r content_file
D};d}' file

For range of lines matching ^foo$ and ^bar$. If line matches ^bar$ swap (empty) hold space into pattern space, read and append content of content_file, then delete pattern space up to first newline and start next cycle with the reminder of the pattern space. For all other lines in that range... just drop the line (delete patter space and move to the next line of input).

Otherwise to the result of your question... any string enclosed in single quotes is taken literally by shell and without any expansion (also of variables) taking place. '${content}' means literally ${content} and that is also part of the argument passed to sed, whereas double quote text ("${content}") would still see shell expand variable to what its value before becoming part of the sed arguments. Since that could still see content tripping up sed, I would opt for the r method for being more generic / robust.


EDIT: Edit keeping the start and end lines in (since I've misread the question):

sed -e '/^foo$/,/^bar$/{/^foo$/{r content_file
p};/^bar$/!d}' file

This time for range between matched of ^foo$ and ^bar$... for opening line matching ^foo$ we it reads content from content_file appending it to pattern space and then prints it (because of delete that follow). Then for all line in the range not matching the closing line pattern ^bar$ it just drops it and moves on.

sed '/foo/,/bar/{//!d;/foo/s//&\n'${content}'/}' file

From foo to bar, delete lines not matching previous match //!d.

On foo line, replace match & with match followed by \n${content}

This might work for you (GNU sed):

sed '/foo/!b;:a;$b;N;/bar/!ba;P;s/.*\n//;e cat contentFile' file

Print all lines until one containing foo.

If this is the last line, then there will never be a line containing bar so break out and do not insert the contentFile.

Otherwise, append the next line and check for it containing bar, if not repeat.

The pattern space should now contain both foo and bar so, print the first line (containing foo), remove all other lines other than the one containing bar, print the file contentFile and then print the last line of the collection containing bar.

N.B. This does not insert the contentFile unless both foo and bar exist in file. Also the e command will evaluate the cat contentFile immediately and insert the result into the output stream before printing the line containing bar, whereas the r command always prints to the output stream after the implicit print of the sed cycle. An alternative:

sed -ne '/foo/{p;:a;n;/bar/!ba;e cat contentFile' -e '};p' file

However this solution will only print lines before foo if file does not have a line containing bar.

Related