使用xmlstarlet以特定顺序解析列表

时间:2019-02-28 16:43:11

标签: xml bash sorting xslt xmlstarlet

我正在尝试使用xmlstarlet sel列出我需要从xml文件创建的磁盘分区,该分区在磁盘上的升序块位置中将它们列出(示例:https://github.com/finley/SystemImager/blob/initrd-from-imageserver-and-dont-package-initrd/doc/examples/disk-layout-complex.xml) 该文件是通过转储必须复制的已安装系统生成的。然后,用户可以用“ *”代替要更改为新磁盘的分区的大小。

现在我正在执行以下操作:

    local IFS=;
    DISK_DEV=sda
    DISKS_LAYOUT_FILE=/tmp/disk-layout-complex.xml
    cd /tmp
    wget https://raw.githubusercontent.com/finley/SystemImager/initrd-from-imageserver-and-dont-package-initrd/doc/examples/disk-layout-complex.xml
    xmlstarlet sel -t -m "config/disk[@dev=\"${DISK_DEV}\"]/part" -v "concat(@num,';',@size,';',@p_type,';',@id,';',@p_name,';',@flags,';',@lvm_group,';',@raid_dev)" -n ${DISKS_LAYOUT_FILE} | sed '/^\s*$/d' |\
    while read P_NUM P_SIZE P_TYPE P_ID P_NAME P_FLAGS P_LVM_GROUP P_RAID_DEV
    do
        # process partitions creation.
        echo "Creating partition $P_NUM of size $P_SIZE tpye $P_TYPE"
    done

上面的xmlstarlet将产生以下输出,然后由while读取循环处理:

    1;500;primary;;;boot;;
    3;4096;primary;;;;;;
    4;*;extended;;;;;;
    7;4096;logical;;;;;;
    5;*;logical;;;;;;
    6;2048;logical;;;;;;
    2;1024;primary;;;swap;;

处理完第3行(分区#4)后,磁盘上没有剩余空间,该循环将处理第4行(分区#7),并且将失败,并且磁盘上没有剩余空间。

问题出在可变大小的分区上(使用100%(文件中的“ *”))。如果在其他剩余磁盘之前列出了一个磁盘(在上述情况下为第4部分),那么它将创建为具有全部剩余空间,而磁盘上没有剩余空间来处理最后一个磁盘。因此,例如,不可能将主交换分区放在带有大小可变的/分区的磁盘末尾。

问:有没有一种聪明的方法可以使用xmlstarlet sel按以下顺序列出分区:

以与xml文件中写入顺序相同的顺序列出所有主分区和扩展分区,直到看到大小为“ *”的分区为止。

  • 记住这个可变大小的分区
  • 然后从头开始按相反顺序列出其他分区
  • 最后打印可变大小的分区
  • 使用逻辑分区(如果有)重复操作

对于所有分区,添加一个字段,说明该字段是按顺序列出还是按相反顺序列出,这样我就可以知道是否必须相对于可用空间的开头或相对于可用空间的结尾创建分区。 (可变分区将从可用空间的开头开始就被标记为正常顺序)

对于列出的示例(disk-layout-complex.xml),这将按以下顺序列出要创建的分区: (下面,xmlstarlet的输出将由类似于上面的代码的while读取循环处理,但带有一个以上的读取参数OFFSET_CREATE,它将读取正向/反向值)

    normal;1;500;primary;;;boot;;
    normal;3;4096;primary;;;;;;
    reverse;2;1024;primary;;;swap;;
    normal;4;*;extended;;;;;;
    normal;7;4096;logical;;;;;;
    reverse;6;2048;logical;;;;;;
    normal;5;*;logical;;;;;;

处理上面的xmlstarlet输出将永远不会触发以下情况:如果要创建一些分区而磁盘没有剩余空间,因为创建的分区具有100%的剩余空间。

我正在特制的initrd中对此进行处理,因此我只能访问sed / grep / bash2 / xmlstarlet / awk等最常见的utils。没有perl,没有python,没有一般需要库的语言。

我非常相信,有一种解决方案可以完成大部分任务,即使不是全部,但我远远不够熟练,甚至无法评估是否可以通过这种方式完成。我想我可以用纯bash来实现,但这不会那么优雅。

4 个答案:

答案 0 :(得分:0)

在广泛地搜索和学习了xslt的基础知识之后,看来这就是我想要的。

(要处理的xml文件的完整源可在这里找到:https://github.com/finley/SystemImager/blob/initrd-from-imageserver-and-dont-package-initrd/doc/examples/disk-layout-complex.xml

<!— Sample cut, relevant for this question —>
<config>
      <disk dev="/dev/sda" label_type="msdos" unit_of_measurement="MiB">
              <part num="1" size="500" p_type="primary" flags="boot" />
              <part num="3" size="4096" p_type="primary" />
              <part num="4" size="*" p_type="extended" />
              <part num="7" size="4096" p_type="logical" />
              <part num="5" size="*" p_type="logical" />
              <part num="6" size="2048" p_type="logical" />
              <part num="2" size="1024" p_type="primary" flags="swap" />
      </disk>
</config>

xslt没有变量,因此我需要一个递归算法来实现智能排序,该排序将导致正确的分区创建顺序。

目标是让xmlstarlet tr layout.xslt disk-layout.xml产生以下输出:

normal;1;500;primary;;;boot;;
normal;3;4096;primary;;;;;;
reverse;2;1024;primary;;;swap;;
normal;4;*;extended;;;;;;
normal;7;4096;logical;;;;;;
reverse;6;2048;logical;;;;;;
normal;5;*;logical;;;;;;

递归算法看起来像:

do_partition_template(index, type)
  if partition(index) is same type and if variable_partition is not yet seen:
    print "normal;line"
    do_partition_template(index+1,type)
  if partition(index) is same type and if variable partition has been seen:
    do_partition_template(index+1, type)
    print "reverse;line"
  if(partition(index) is same type and if  size(partition)="*":
    do_partition_template(index+1, type)
    print "normal;line"
  if partition is not the same type:
    do_partition_template(index+1, type)
  fi
call template 1st partition type='primary|extended'
call template 1st partition type='logical'

我不确定算法的递归形式,但是需要按顺序创建的是: -从磁盘开始创建的主/扩展分区列表 -从磁盘末端创建的主/扩展分区列表 -具有size =“ ”的主/扩展分区 -从扩展分区的开头创建的逻辑分区的列表 -从扩展分区末尾创建的逻辑分区列表 -大小为“

的逻辑分区

作为xsl中的newbee,解决这个问题非常复杂。 我使用xmlstarlet sel -C -t -m“ config / disk [@dev = \” / dev / sda \“] / part” -v“ concat(@num,';',@ size,';',@ p_type,';',@ id,';',@ p_name,';',@ flags,';',@ lvm_group,';',@ raid_dev)“ -n ./disk-layout-complex.xml> do_part.xslt 首先要解决我的问题,但是接下来我很难正式确定一个模板,它会在2次扫描中击中所有分区(一个是主分区/扩展分区,一个是逻辑分区)...

在没有变量的情况下工作并不容易。

答案 1 :(得分:0)

最终答案如下。

<!— Sample cut, relevant for this question —>
<config>
      <disk dev="/dev/sda" label_type="msdos" unit_of_measurement="MiB">
              <part num="1" size="500" p_type="primary" flags="boot" />
              <part num="3" size="4096" p_type="primary" />
              <part num="4" size="*" p_type="extended" />
              <part num="7" size="4096" p_type="logical" />
              <part num="5" size="*" p_type="logical" />
              <part num="6" size="2048" p_type="logical" />
              <part num="2" size="1024" p_type="primary" flags="swap" />
      </disk>
</config>

我们想要以下输出以便按顺序列出分区,例如我们可以按照磁盘上的出现顺序创建分区。大小为“ *”的分区将占用所有剩余空间。因此,必须最后创建它。

因此,我们需要按顺序创建以下分区。 (开头和结尾是参考,告诉我们是否需要相对于可用空间的开头或相对于可用空间的结尾创建分区)

/dev/sda;end;2;1024;MiB;primary;;;swap;;
/dev/sda;beginning;1;500;MiB;primary;;;boot;;
/dev/sda;beginning;3;4096;MiB;primary;;;;;
/dev/sda;beginning;4;*;MiB;extended;;;;;
/dev/sda;end;6;2048;MiB;logical;;;;;
/dev/sda;beginning;7;4096;MiB;logical;;;;;
/dev/sda;beginning;5;*;MiB;logical;;;;;

通过使用以下命令运行sxl转换文件即可获得:

xmlstarlet tr do_part.xsl ./disk-layout-complex.xml

现在是代码:

<?xml version="1.0" encoding="UTF-8"?>
<!-- Call me with:
     xmlstarlet tr do_part.xsl disk-layout.xml

     Output: List of partitions to create in order.
        Each line list the following values separated by semicolons:
        - disk device
        - creation reference
        - partition number
        - partition size
        - partition size unit
        - partition type
        - partition id
        - partition name
        - partition flags
        - lvm group it belongs to
        - raid device it belongs to

      Author: Olivier LAHAYE (c) 2019
      Licence: GPLv2
-->
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:exslt="http://exslt.org/common" version="1.0" extension-element-prefixes="exslt">
  <xsl:output method="text" omit-xml-declaration="yes" indent="no"/>
  <xsl:strip-space elements="*"/>
  <xsl:template match="/config/disk"> <!-- We are loking for disk informations only -->
    <!-- For each disk block -->
    <xsl:call-template name="PrintPartition"> <!-- Compute primary or extended partitions to create -->
      <xsl:with-param name="index"><xsl:value-of select="count(part)"/></xsl:with-param>
      <xsl:with-param name="reference">end</xsl:with-param>
      <xsl:with-param name="type">primary|extended</xsl:with-param>
    </xsl:call-template>
    <xsl:call-template name="PrintPartition"> <!-- then, compute logical partitions to create -->
      <xsl:with-param name="index"><xsl:value-of select="count(part)"/></xsl:with-param>
      <xsl:with-param name="reference">end</xsl:with-param>
      <xsl:with-param name="type">logical</xsl:with-param>
    </xsl:call-template>
  </xsl:template> <!-- We're done -->

  <!-- Main recursive template that will dump partitions to create for the matched disk -->
  <xsl:template name="PrintPartition">
    <xsl:param name="index"/> <!-- partition node number within disk item-->
    <xsl:param name="reference"/> <!-- beginning or end: should we create partition relative from beginning or from end of free space -->
    <xsl:param name="type"/> <!-- type of partitions -->
    <xsl:choose>
      <xsl:when test="$index=1">
        <xsl:if test="contains($type,part[position()=$index]/@p_type)">
          <xsl:value-of select="concat(@dev,';',$reference,';',part[position()=$index]/@num,';',part[position()=$index]/@size,';',@unit_of_measurement,';',part[position()=$index]/@p_type,';',part[position()=$index]/@id,';',part[position()=$index]/@p_name,';',part[position()=$index]/@flags,';',part[position()=$index]/@lvm_group,';',part[position()=$index]/@raid_dev,'&#10;')"/> <!-- write partition information -->
        </xsl:if>
      </xsl:when>
      <xsl:when test="contains($type,part[position()=$index]/@p_type) and part[position()=$index]/@size!='*'">
        <xsl:if test="$reference='end'">
          <xsl:value-of select="concat(@dev,';',$reference,';',part[position()=$index]/@num,';',part[position()=$index]/@size,';',@unit_of_measurement,';',part[position()=$index]/@p_type,';',part[position()=$index]/@id,';',part[position()=$index]/@p_name,';',part[position()=$index]/@flags,';',part[position()=$index]/@lvm_group,';',part[position()=$index]/@raid_dev,'&#10;')"/> <!-- write partition information -->
        </xsl:if>
        <xsl:call-template name="PrintPartition">
          <xsl:with-param name="index"><xsl:value-of select="number($index)-1"/></xsl:with-param>
          <xsl:with-param name="reference"><xsl:value-of select="$reference"/></xsl:with-param>
          <xsl:with-param name="type"><xsl:value-of select="$type"/></xsl:with-param>
        </xsl:call-template>
        <xsl:if test="$reference='beginning'">
          <xsl:value-of select="concat(@dev,';',$reference,';',part[position()=$index]/@num,';',part[position()=$index]/@size,';',@unit_of_measurement,';',part[position()=$index]/@p_type,';',part[position()=$index]/@id,';',part[position()=$index]/@p_name,';',part[position()=$index]/@flags,';',part[position()=$index]/@lvm_group,';',part[position()=$index]/@raid_dev,'&#10;')"/> <!-- write partition information -->
        </xsl:if>
      </xsl:when>
      <xsl:when test="contains($type,part[position()=$index]/@p_type) and part[position()=$index]/@size='*'">
        <xsl:call-template name="PrintPartition">
          <xsl:with-param name="index"><xsl:value-of select="number($index)-1"/></xsl:with-param>
          <xsl:with-param name="reference">beginning</xsl:with-param>
          <xsl:with-param name="type"><xsl:value-of select="$type"/></xsl:with-param>
        </xsl:call-template>
        <xsl:value-of select="concat(@dev,';','beginning;',part[position()=$index]/@num,';',part[position()=$index]/@size,';',@unit_of_measurement,';',part[position()=$index]/@p_type,';',part[position()=$index]/@id,';',part[position()=$index]/@p_name,';',part[position()=$index]/@flags,';',part[position()=$index]/@lvm_group,';',part[position()=$index]/@raid_dev,'&#10;')"/> <!-- write partition information -->
      </xsl:when>
      <xsl:otherwise>
        <xsl:call-template name="PrintPartition">
          <xsl:with-param name="index"><xsl:value-of select="number($index)-1"/></xsl:with-param>
          <xsl:with-param name="reference"><xsl:value-of select="$reference"/></xsl:with-param>
          <xsl:with-param name="type"><xsl:value-of select="$type"/></xsl:with-param>
        </xsl:call-template>
      </xsl:otherwise>
    </xsl:choose>
  </xsl:template>
</xsl:stylesheet>

瞧,它完美地工作。

当然有更好或更优雅的解决方案。 (随时发表评论)

  • concat的输出很丑陋,但我不知道如何使它看起来更性感
  • contains($ type,part [position()= $ index] / @ p_type)用于测试$ type(分区类型)是否匹配(test =“ @ p_type = $ type”和$ type包含“ primary |扩展”或“逻辑”)
  • 当我以相反的顺序(从最后一个元素到第一个元素)导航时,我不得不使用position()= $ index(这是我发现以相反的顺序进行foreach的最佳方式)
  • 我需要修复一些错误:(如果在磁盘节中未声明任何分区,则会发生无限循环,相对于磁盘末端会创建唯一的可变大小分区。虽然可行,但这并不是最佳选择)

(此处提供的代码(更改不多):https://github.com/finley/SystemImager/blob/initrd-from-imageserver-and-dont-package-initrd/lib/dracut/modules.d/51systemimager/do_partitions.xsl

答案 2 :(得分:0)

如果我对您的理解正确,则可以按以下方式实现所需的排序顺序(为清楚起见,代码已缩短):

DISK_DEV=/dev/sda
DISKS_LAYOUT_FILE=./disk-layout-complex.xml
xmlstarlet sel --text -t \
    -m "config/disk[@dev=\"${DISK_DEV}\"]" \
    -m "part[@p_type='primary']"  -s A:N:L @p_type \
    -v "concat(@num,';',@size,';',@p_type,';',@flags)" -n -b \
    -m "part[@p_type='extended']" -s D:N:L @p_type \
    -v "concat(@num,';',@size,';',@p_type,';',@flags)" -n -b \
    -m "part[@p_type='logical']"  -s D:N:L @size \
    -v "concat(@num,';',@size,';',@p_type,';',@flags)" -n -b \
    ${DISKS_LAYOUT_FILE}

输出:

1;500;primary;boot
3;4096;primary;
2;1024;primary;swap
4;*;extended;
7;4096;logical;
6;2048;logical;
5;*;logical;

请记住,您可以在XSLT中使用多个排序键,例如...     -m "part[@p_type='primary']" -s A:N:L @p_type -s A:T:L @flags

答案 3 :(得分:0)

我又看了这个。您指定的列表分为 2 x 3组:

  1. 可变大小的分区(如果有)之前的主/扩展分区, 按文档顺序
  2. 一个可变大小的主分区/扩展分区(如果有), 以相反的文档顺序
  3. 文档中大小可变的
  4. 主分区/扩展分区(如果有) 订购
  5. 为1.,但用于逻辑分区
  6. 与2.一样,但对于逻辑分区
  7. 与3.一样,但用于逻辑分区

这是一个复杂的xmlstarlet命令,其中包含6个XPath节点测试和 插入一些shell变量(请谨慎使用引号)以减少重复。在情况2和5中,如果不存在可变大小的分区,则额外的谓词-[boolean(...)]-防止双重输出。如果未声明任何分区,则不会生成任何输出。

#!/bin/sh
disksLayoutFile="${DISKS_LAYOUT_FILE:-./disk-layout-complex.xml}"
diskDev="${DISK_DEV:-/dev/sda}"
#
cfgDisk="config/disk[@dev='${diskDev}']"
type_PE="(@p_type='primary' or @p_type='extended')"
type__L="(@p_type='logical')"
preSibVar_PE="preceding-sibling::part[${type_PE}]/@size='*'"
folSibVar_PE="following-sibling::part[${type_PE}]/@size='*'"
preSibVar__L="preceding-sibling::part[${type__L}]/@size='*'"
folSibVar__L="following-sibling::part[${type__L}]/@size='*'"
valueList="';',@num,';',@size,';',@p_type,';',@flags,';'"
#
xmlstarlet sel --text --template \
    -m "${cfgDisk}/part[${type_PE}][@size!='*'][not(${preSibVar_PE})]" \
    -v "concat('beg',${valueList})" -n -b \
    -m "${cfgDisk}/part[${type_PE}][@size!='*'][not(${folSibVar_PE})][boolean(${preSibVar_PE})]" \
    -s 'D:N:L' 'count(preceding-sibling::*)' \
    -v "concat('end',${valueList})" -n -b \
    -m "${cfgDisk}/part[${type_PE}][@size='*']" \
    -v "concat('beg',${valueList})" -n -b \
    -m "${cfgDisk}/part[${type__L}][@size!='*'][not(${preSibVar__L})]" \
    -v "concat('beg',${valueList})" -n -b \
    -m "${cfgDisk}/part[${type__L}][@size!='*'][not(${folSibVar__L})][boolean(${preSibVar__L})]" \
    -s 'D:N:L' 'count(preceding-sibling::*)' \
    -v "concat('end',${valueList})" -n -b \
    -m "${cfgDisk}/part[${type__L}][@size='*']" \
    -v "concat('beg',${valueList})" -n -b \
    "${disksLayoutFile}"

输出:

beg;1;500;primary;boot;
beg;3;4096;primary;;
end;2;1024;primary;swap;
beg;4;*;extended;;
beg;7;4096;logical;;
end;6;2048;logical;;
beg;5;*;logical;;

虽然有可能增加此命令的复杂性 (例如磁盘/分区布局问题),我认为最好放置 在适当的XSLT脚本中;突破shell + xmlstarlet使用限制 已经。