I try to create a shell script that searches in an XML file for an attribute and create an element with the given attribute if this doesn't exist or delete the element if the attribute exists.
Here is the XML File:
<configuration name="distributor.conf" description="Distributor Configuration">
<lists>
<list name="CRproductionLoadshare">
<node name="fs100" weight="2"/>
<node name="fs101" weight="2"/>
</list>
<list name="AnyOtherGroup">
<node name="fs100" weight="2"/>
</list>
</lists>
</configuration>
And this is my Shellscript so far:
fs_name=fs
cnt=102
xmlstarlet ed \
--var fs "'$fs_name$cnt'" \
-a '//list' -t elem -n node -v "$fs_name$cnt" \
-i '//node' -t attr -n name -v "$fs_name$cnt" \
-i '//node' -t attr -n weight -v 2 \
-d '//node[.=$fs]/text()' <distributor.conf.xml
The expected Output is
<configuration name="distributor.conf" description="Distributor Configuration">
<lists>
<list name="CRproductionLoadshare">
<node name="fs100" weight="2"/>
<node name="fs101" weight="2"/>
<node name="fs102" weight="2"/>
</list>
<list name="AnyOtherGroup">
<node name="fs100" weight="2"/>
</list>
</lists>
</configuration>
But my script work like this:
<?xml version="1.0"?>
<configuration name="distributor.conf" description="Distributor Configuration">
<lists>
<list name="CRproductionLoadshare">
<node name="fs100" weight="2" name="fs102" weight="2"/>
<node name="fs101" weight="2" name="fs102" weight="2"/>
</list>
<list name="AnyOtherGroup">
<node name="fs100" weight="2" name="fs102" weight="2"/>
</list>
<node name="fs102" weight="2"/>
</lists>
</configuration>
How to change the shell script to reach the goal. At first, I want to add the node name="fs102" in case of that this node didn't exist.
The most difficult task at hand here is to build the XPath that selects the correct node.
Step 1: find the XPath you need
example 1: Select the node named list
who has an attribute @name="CRproductionLoadshare"
and has a child named node
with attribute @name="fs100"
.
So you can search for the parent of that particular node named node
.
$ xmlstarlet sel -t \
-m '//node[@name="fs100"]/parent::list[@name="CRproductionLoadshare"]' \
-c . -n foo.xml
<list name="CRproductionLoadshare">
<node name="fs100" weight="2"/>
<node name="fs101" weight="2"/>
</list>
or a bit easier :
$ xmlstarlet sel -t \
-m '//list[@name="CRproductionLoadshare" and node[@name="fs100"]]' \
-c . -n foo.xml
example 2: Select the node named list
who has an attribute @name="CRproductionLoadshare"
and does not have a child named node
with attribute @name="fs102"
.
Here we can use the XPath not
-function
$ xmlstarlet sel -t \
-m '//list[@name="CRproductionLoadshare" and not(node[@name="fs102"])]' \
-c . -n foo.xml
<list name="CRproductionLoadshare">
<node name="fs100" weight="2"/>
<node name="fs101" weight="2"/>
</list>
Step 2: edit your XML-file with the XPath you just found
A: Just add the node if it is not there
So, since you now know the correct XPath to select the node, you can edit the XML-file accordingly by first inserting a subnode -s
and then updating its values and attributes with -i
$ xpath1='//list[@name="CRproductionLoadshare" and not(node[@name="fs102"])]'
$ xpath2='//list[@name="CRproductionLoadshare" and not(node[@name="fs102" and @weight="2"])]/node[last()]'
$ xmlstarlet ed -s ${xpath1} -t elem -n "node" -v "" \
-i ${xpath2} -t attr -n "name" -v "fs102" \
-i ${xpath2} -t attr -n "weight" -v "2" \
foo.xml
which outputs
<configuration name="distributor.conf" description="Distributor Configuration">
<lists>
<list name="CRproductionLoadshare">
<node name="fs100" weight="2"/>
<node name="fs101" weight="2"/>
<node name="fs102" weight="2"/>
</list>
<list name="AnyOtherGroup">
<node name="fs100" weight="2"/>
</list>
</lists>
</configuration>
B: Toggle the node
Toggling can be done by adding a fake attribute and then remove the node with that attribute:
$ xpath0='//list[@name="CRproductionLoadshare"]/node[@name="fs102"]'
$ xpath1='//list[@name="CRproductionLoadshare" and not(node[@name="fs102" and @delete="1"])]'
$ xpath2='//list[@name="CRproductionLoadshare" and not(node[@name="fs102" and @delete="1"])]/node[last()]'
$ xpath3='//list[@name="CRproductionLoadshare"]/node[@name="fs102" and @delete="1"]'
$ xmlstarlet ed -i ${xpath0} -t attr -n "delete" -v "1" \
-s ${xpath1} -t elem -n "node" -v "" \
-i ${xpath2} -t attr -n "name" -v "fs102" \
-i ${xpath2} -t attr -n "weight" -v "2" \
-d ${xpath3} \
foo.xml
search in a XML file for a attribute and create it if this doesn't
exist
fs_name="fs"
cnt=102
node_exists=$(xmlstarlet sel -t --var fs="'${fs_name}$cnt'" -v 'boolean(//list[@name="CRproductionLoadshare"]/node[@name=$fs])' distributor.conf.xml)
[ "$node_exists" = "false" ] && xmlstarlet ed -O -s '//list[@name="CRproductionLoadshare"]' \
-t elem -n node -i '//list[@name="CRproductionLoadshare"]/node[last()]' \
-t attr -n name -v "${fs_name}$cnt" \
-i '//list[@name="CRproductionLoadshare"]/node[last()]' -t attr -n weight -v 2 distributor.conf.xml
The output:
<configuration name="distributor.conf" description="Distributor Configuration">
<lists>
<list name="CRproductionLoadshare">
<node name="fs100" weight="2"/>
<node name="fs101" weight="2"/>
<node name="fs102" weight="2"/>
</list>
<list name="AnyOtherGroup">
<node name="fs100" weight="2"/>
</list>
</lists>
</configuration>
Scheme:
node_exists
is assigned with boolean value indicating the needed node existence
[ "$node_exists" = "false" ] && xmlstarlet ed ...
- the 2nd xmlstarlet
edit command will be only executed if the node_exists
is not equal to false
The shell script that works for toggle the node looks like this:
fs_name=fs
cnt=102
inputfile=distributor.conf.xml
if [ -n "$(xmlstarlet sel -T -t -v "//list[@name='CRproductionLoadshare']/node[@name='$fs_name$cnt']/@name" $inputfile)" ]; then
echo "$fs_name$cnt already defined in $inputfile"
xmlstarlet ed -L -d "//list[@name='CRproductionLoadshare']/node[@name='$fs_name$cnt']" $inputfile
else
echo "adding $fs_name$cnt to $inputfile"
xmlstarlet ed -L -s "//list[@name='CRproductionLoadshare']" -t elem -n TempNode -v "" \
-i //TempNode -t attr -n "name" -v "$fs_name$cnt" \
-i //TempNode -t attr -n "weight" -v "2" \
-r //TempNode -v node \
$inputfile
fi
Each time I run that script the inputfile toggle the node (add/delete) only in the list element with name CRproductionLoadshare.