如何使用 xmlstarlet 搜索 XML 节点并添加或删除它

How to search for a XML node and add or delete it using 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" 以防该节点不存在。

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

输出:

<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 edit 命令只会在 node_exists 不等于 false 时执行

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

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

每次我 运行 编写输入文件脚本时,仅在名称为 CRproductionLoadshare 的列表元素中切换节点 (add/delete)。

手头最困难的任务是构建 select 正确节点的 XPath。

第 1 步:找到您需要的 XPath

示例 1: Select 名为 list 的节点具有属性 @name="CRproductionLoadshare" 并且具有名为 child 的节点 node 具有属性 @name="fs100".

因此您可以搜索名为 node 的特定节点的 parent。

$ 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: Select 名为 list 的节点具有属性 @name="CRproductionLoadshare" 没有 有一个名为 node 的 child 属性 @name="fs102".

这里我们可以使用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>

第 2 步:使用您刚刚找到的 XPath 编辑您的 XML-file

答:没有节点就添加

因此,由于您现在知道 select 节点的正确 XPath,因此您可以通过首先插入子节点 -s 然后更新其值和属性来相应地编辑 XML-file -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