xmlxsltxmlstarletxmllintxq

Duplicate a section of XML in bash (via xmlstartlet, xmllint, xq, xslt, ...)


I have a valid XML file. I would like to duplicate a tag with contents via linux shell command(s).

By duplication I mean: copy <x>...</x> entirely, then paste right after the original.

I do not have any prior knowledge of the contents or structure inside of the tag being duplicated, but I do know XPath to that tag.

So far I have achieved required result with sed via regex. I would like to use appropriate tools like xmlstarlet or xmllint instead, tools from official debian repo are preferred.

It seems the task is achievable this way:

  1. extract a segment of XML into another file
  2. concatenate that file to the original file
  3. find the inserted section and move it to the desired position

To me that procedure seems a little convoluted, is there any better way?

Example input XML:

<?xml version="1.0" encoding="UTF-8"?>
<root>
 <item>
  <someVariedContents/>
 </item>
</root>

Example output XML:

<?xml version="1.0" encoding="UTF-8"?>
<root>
 <item>
  <someVariedContents/>
 </item>
 <item>
  <someVariedContents/>
 </item>
</root>

Solution

  • To duplicate an element using xmlstarlet edit, for example:

    # shellcheck shell=sh disable=SC2016
    
    xmlstarlet edit \
      --var F 'root/item[1]' \
      -a '$F' -t 'elem' -n 'item' -v '' \
      -u '$prev' -x '$F/node() | $F/@*' \
    file.xml
    

    In an xmlstarlet edit command --var defines a named variable, and the back reference $prev variable (aka $xstar:prev) refers to the node(s) created by the most recent -s, -i, or -a option which all define or redefine it (see xmlstarlet.txt for a few examples of --var and $prev).


    To process a multi-member node-set rather than a single element you can use the `-x` option with a relative XPath expression; for an example see this.