如何搜索XML节点并使用xmlstarlet

时间:2018-05-30 06:35:32

标签: xml shell scripting xmlstarlet

我尝试创建一个shell脚本,在XML文件中搜索属性,如果不存在则创建具有给定属性的元素,或者如果属性存在则删除元素。

这是XML文件:

<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>

到目前为止,这是我的Shellscript:

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

预期的输出

<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>

但我的脚本是这样的:

<?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>

如何更改shell脚本以达到目标。首先,我想添加节点名称=“fs102”,以防该节点不存在。

3 个答案:

答案 0 :(得分:2)

  

在XML文件中搜索属性,如果没有,则创建它   存在

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

输出:

<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>

体系:

  • node_exists被赋予布尔值,表示所需节点存在
  • [ "$node_exists" = "false" ] && xmlstarlet ed ... - 仅当xmlstarlet不等于node_exists
  • 时,才会执行第二个false 编辑命令

答案 1 :(得分:1)

这里最困难的任务是构建选择正确节点的XPath。

第1步:找到您需要的XPath

示例1:选择名为list的节点,该节点具有属性@name="CRproductionLoadshare",并且具有名为node的子节点,其属性为@name="fs100"。< / p>

因此,您可以搜索名为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>

或者更容易:

$ xmlstarlet sel -t                                                        \ 
       -m '//list[@name="CRproductionLoadshare" and node[@name="fs100"]]'  \
       -c . -n foo.xml

示例2 :选择名为list的节点,其属性为@name="CRproductionLoadshare"的子节点为node属性@name="fs102"

这里我们可以使用XPath not - 函数

$ 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>

步骤2:使用刚刚找到的XPath编辑XML文件

答:只需添加节点

即可

因此,既然您现在知道选择节点的正确XPath,您可以通过首先插入子节点-s然后用-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

输出

<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:切换节点

切换可以通过添加假属性来完成,然后删除具有该属性的节点:

$ 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 :(得分:0)

用于切换节点的shell脚本如下所示:

template<typename T>
class A {};

class A {};    //compilation fails when uncommented

template<typename T>
void func();    //No problem compiling

void func();

int main() {

}

每次运行该脚本时,inputfile仅在名为CRproductionLoadshare的list元素中切换节点(添加/删除)。