How to search for a XML node and add or delete it using xmlstarlet

戏子无情 提交于 2019-12-23 02:20:17

问题


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.


回答1:


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



回答2:


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



回答3:


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.



来源:https://stackoverflow.com/questions/50597814/how-to-search-for-a-xml-node-and-add-or-delete-it-using-xmlstarlet

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!