devios1
devios1

Reputation: 38005

Bash/sed string replace with contents of file (containing newlines)

Question for the Bash ninjas: I have an XML template file that has some template markers in the form {{VARIABLE_NAME}} and want to find/replace all occurences of that marker with the contents of a text file.

So imagine template.xml looks something like this:

<?xml>
<root>
    <description>
        {{DESCRIPTION}}
    </description>
</root>

And I have a text file called description.txt that could potentially contain any characters (let's assume it's already xml-safe for simplicity). I want to replace the marker with the contents of the file, supporting newlines and other characters.

Previously I've been doing this in my code like this (which works great for simple strings):

sed -i '' -e "s/{{DESCRIPTION}}/$DESCRIPTION/g" template.xml

However, if $DESCRIPTION contains newlines, sed balks:

sed: 1: "s/{{DESCRIPTION}}/Hello w ...": unescaped newline inside substitute pattern

Is there a better way to do this, or should I first search/replace all newlines with escaped variants in the $DESCRIPTION variable first. And are there any other characters I need to escape, like /?

Upvotes: 3

Views: 1489

Answers (3)

devios1
devios1

Reputation: 38005

Thanks for the answers. Always interested in learning new ways to do things in Bash. I ended up solving it in a different way, however, by escaping the string using Bash's built-in replace notation, which I just discovered (sed failed at this for reasons I'm still not quite clear on):

DESCRIPTION=`cat description.txt`
DESCRIPTION=${DESCRIPTION//$'\n'/\\$'\n'} # escape newlines
DESCRIPTION=${DESCRIPTION//\//\\\/} # escape slashes
DESCRIPTION=${INPUT//$'&'/\\$'&'amp;} # escape ampersands
# other xml-escaping...

...and then using sed to actually do the replacement inline in output.xml:

cp -f template.xml output.xml
sed -i '' -e "s/{{DESCRIPTION}}/$DESCRIPTION/g" output.xml

Since the newlines and slashes are now escaped (am I missing anything??) the sed command accepts the replacement string without complaint and it renders the output file perfectly.

Upvotes: 2

John1024
John1024

Reputation: 113844

Here is a BASH (sh, actually) script to do the replacement:

#!/bin/sh
while IFS= read -r line
do
    case "$line" in
        *{{DESCRIPTION}})
            cat "description.txt"
            ;;
        *)
            echo "$line"
            ;;
    esac
done <"template.xml"

In the problem statement, "{{DESCRIPTION}}" appears on a line by itself. If that is not the case in general, some minor changes would be needed to preserved the other data on the line.

Upvotes: 2

Kent
Kent

Reputation: 195059

use awk:

awk -v RS='\0' 'NR==FNR{r=$0;next}{sub(/\{\{DESCRIPTION\}\}/,r)}7' desc temp.xml

I think you know how to play with redirection to save the output into a file.

test: in the example, file f contains many "special" chars

kent$  cat f.xml
<?xml>
<root>
    <description>
        {{DESCRIPTION}}
    </description>
</root>

kent$  cat f
foo
'
"
O"'
/
\



        - 
bar

kent$  awk -v RS='\0' 'NR==FNR{r=$0;next}{sub(/\{\{DESCRIPTION\}\}/,r)}7' f f.xml
<?xml>
<root>
    <description>
        foo
'
"
O"'
/
\



        - 
bar

    </description>
</root>

Upvotes: 1

Related Questions