1

I have a heredoc with xml content, and I add the content of another file like this:

bar file:

<bar>
  $baz
</bar>

the script:

bar=$(cat bar.xml)

# this would be the ideal, of course it doesn't work
baz=$(cat baz.xml)
#

cat << EOF > out.xml
<foo>
  $bar
</foo>
EOF

The output of out.xml:

<foo>
  <bar>
    $baz
  </bar>
</foo>

Now I want to add a third file to the contents of bar:

baz file:

<baz>baz<baz>

so the final content of out.xml would be:

<foo>
  <bar>
    <baz>baz</baz>
  </bar>
</foo>

The workflow supposes that I can strip (a large) xml file into components, each one with its own components.

master (heredoc) xml
  ^^^
  second xml
    ^^^
    third xml

I don't know where to start as I want to keep it as simple as possible, so any help appreciated.

2
  • You should rather use an include-able preprocessor like cpp -P or m4. envsubst (from the gettext-base package) would probably also work for your very narrow XY-zied case ;-)
    – user414777
    Oct 31 '20 at 0:15
  • Thank you! I would prefer to keep it as bashist as possible. Oct 31 '20 at 0:19
2

The idiomatic way to combine XML documents is using XSLT. First, some setup:

$ cd "$(mktemp --directory)"
$ echo '<foo/>' > ./foo.xml
$ echo '<bar/>' > ./bar.xml
$ echo '<baz/>' > ./baz.xml

Then we create an XSLT file which will insert the contents of one file into an element in the source file, copying the rest of the file as-is:

$ cat > ./insert.xslt <<'EOF'
> <xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
>     <xsl:template match="@* | node()">
>         <xsl:copy>
>             <xsl:apply-templates select="@* | node()"/>
>             <xsl:if test="name() = $element">
>                 <xsl:copy-of select="document($file)"/>
>             </xsl:if>
>         </xsl:copy>
>     </xsl:template>
> </xsl:stylesheet>
> EOF

Now we can insert an arbitrary file into another:

$ xsltproc --output ./bar-insert.xml --stringparam file ./baz.xml --stringparam element bar ./insert.xslt ./bar.xml
$ cat ./bar-insert.xml
<?xml version="1.0"?>
<bar><baz/></bar>

Let's repeat to create the final file:

$ xsltproc --output ./foo-insert.xml --stringparam file ./bar-insert.xml --stringparam element foo ./insert.xslt ./foo.xml
$ cat ./foo-insert.xml
<?xml version="1.0"?>
<foo><bar><baz/></bar></foo>

The nice thing about this is that it'll work on any XML files, whether they are indented, whether empty elements are collapsed, you name it.


It will even insert the file more than once if it's referenced more than once. For example, given people.xml:

<people>
    <managers>
        <name>Jane Doe</name>
        <reports>
            <insert/>
        </reports>
    </managers>
    <interns>
        <insert/>
    </interns>
</people>

and insert.xml:

<name>Abe Lincoln</name>

We can now put insert.xml in both locations trivially:

$ xsltproc --stringparam file ./insert.xml --stringparam element insert ./insert.xslt ./people.xml 
<?xml version="1.0"?>
<people>
    <managers>
        <name>Jane Doe</name>
        <reports>
            <insert><name>Abe Lincoln</name></insert>
        </reports>
    </managers>
    <interns>
        <insert><name>Abe Lincoln</name></insert>
    </interns>
</people>
1

You could use envsubst here:

bar=$(baz=$(cat baz.xml) envsubst '$baz' < bar.xml) envsubst '$bar' << 'EOF'
<foo>
  $bar
</foo>
EOF

That would make sure only $bar/${bar}/$baz/${baz} are expanded and not other expansions ($(cmd), `cmd`, $((arith)), ${x#y}...) or other variables like in the:

bar=$(cat bar.xml)
baz=$(cat baz.xml)
eval "cat << EOF
<foo>
  $bar
</foo>
EOF"

approach.

0

I found this solution, I don't know if there's something better:

sed -i '/<bar>/r baz.xml' bar.xml

and then the heredoc:

bar=$(cat bar.xml)

cat << EOF > out.xml
<foo>
  $bar
</foo>
EOF

which outputs the desired result:

<foo>
  <bar>
    <baz>baz</baz>
  </bar>
</foo>

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.