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:
- extract a segment of XML into another file
- concatenate that file to the original file
- 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:
- extract a segment of XML into another file
- concatenate that file to the original file
- 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
|
6 Answers
Reset to default 1To 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 emptyitem
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) -- sinceitem
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);
本文标签:
版权声明:本文标题:Duplicate a section of XML in bash (via xmlstartlet, xmllint, xq, xslt, ...) - Stack Overflow 内容由网友自发贡献,该文观点仅代表作者本人, 转载请联系作者并注明出处:http://www.betaflare.com/web/1744980380a2635780.html, 本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌抄袭侵权/违法违规的内容,一经查实,本站将立刻删除。
"set ${content} ${content}"
in this case. – LMC Commented Mar 6 at 11:54