admin管理员组

文章数量:1435288

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>

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>
Share Improve this question edited Mar 9 at 9:32 Jack White asked Mar 6 at 11:15 Jack WhiteJack White 9285 silver badges8 bronze badges 2
  • 1 Here's something similar stackoverflow/a/77901621/2834978. It could do "set ${content} ${content}" in this case. – LMC Commented Mar 6 at 11:54
  • 1 Thanks for great solutions everyone, wish I could accept all of them! My original intention was to use single xmlstarlet command so that is what I accepted. I added xq and xslt to title and tags so that those answers are more easily findable. – Jack White Commented Mar 9 at 9:37
Add a comment  | 

6 Answers 6

Reset to default 1

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
  • let the F variable refer to the source element (for brevity)
  • -a (aka --append) inserts a new empty item element just after $F
  • unlike --append's -v (--value) the -x (--expr) clause of the -u (--update) option takes an XPath argument, hence the two-step approach
  • -x '$F/node() | $F/@*' makes a deep copy of source's nodes on the child and attribute axes (using a union) -- since item doesn't actually have any attributes | $F/@* can be omitted

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.

There's another XML processing tool that makes this super easy: xsh (and I'm its maintainer).

open file.xml ;
xcopy /root/node() append /root ;
save :b ;

Here's an approach using xq from kislyuk/yq:

xq --xml-dtd --xml-force-list item -x '.root.item |= . + .' file.xml
<?xml version="1.0" encoding="utf-8"?>
<root>
  <item>
    <someVariedContents></someVariedContents>
  </item>
  <item>
    <someVariedContents></someVariedContents>
  </item>
</root>

Explanation:

  • --xml-dtd preserves <?xml version="1.0" encoding="utf-8"?>
  • --xml-force-list item wraps <item> into a JSON array
  • .root.item |= . + . duplicates that array
  • -x transcodes the result back into XML and emits it

Add -i to write the result back into file.xml

XSLT is a native XML API for transformations.

Here is how to implement it via XSLT. It is using a so called Identity Transform XSLT pattern.

It is a very generic solution, and will work for XML of any structure.

This way location of <item> XML element, or even XPath to it, and its children doesn't matter.

And use xsltproc to apply an XSLT stylesheet to an XML file.

Install xsltproc (if not already installed)

sudo apt-get install xsltproc   # For Ubuntu/Debian
sudo yum install xsltproc       # For Red Hat/CentOS

XSLT

<?xml version="1.0"?>
<xsl:stylesheet version="1.0"
                xmlns:xsl="http://www.w3./1999/XSL/Transform">
    <xsl:output omit-xml-declaration="no" indent="yes"
                encoding="utf-8"/>
    <xsl:strip-space elements="*"/>

    <!--Identity transform-->
    <xsl:template match="node()|@*">
        <xsl:copy>
            <xsl:apply-templates select="node()|@*"/>
        </xsl:copy>
    </xsl:template>

    <xsl:template match="item">
        <xsl:copy-of select="."/>
        <xsl:copy-of select="."/>
    </xsl:template>
</xsl:stylesheet>

Bash

xsltproc stylesheet.xslt input.xml -o output.xml

Copy node with xmlstarlet:

# save node to variable
node=$(xmlstarlet select --template --copy-of '//root/item' -n file.xml)

# reuse variable
xmlstarlet edit --subnode '//root' --type text -n '' --value "$node" file.xml \
| xmlstarlet unescape \
| xmlstarlet format

Output:

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

If Perl could be an option:


#!/usr/bin/perl
use strict;
use warnings;
use XML::LibXML;

my $doc = XML::LibXML->load_xml(location => 'input.xml');
my $root = $doc->documentElement();
my $item = ( $root->findnodes('//item') )[0];

# Clone the <item> element
my $new_item = $item->cloneNode(1);

# Append the new <item> element to the root
$root->appendChild($new_item);

$doc->toFile('output.xml', 1);

本文标签: