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

问题描述 投票:1回答:4

我正在尝试使用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。没有perl,没有python,没有一般需要库的语言。

我非常确信有一种解决方案可以完成大部分工作(如果不是全部的话),但是我甚至不足以评估它是否可以通过这种方式完成。我想我可以用纯粹的bash来实现这一点,但这将不那么优雅。

xml bash sorting xslt xmlstarlet
4个回答
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;;;;;;

递归的algorythm看起来像:

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中的新手,解决这个问题对我来说非常复杂。我使用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次扫描中击中所有分区(一个用于主要/扩展分区,另一个用于逻辑分区)...

没有变量的工作在某种程度上并不容易。


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


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

它是通过运行xsl转换文件获得的:

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 | extended'或“逻辑”)
  • 我必须使用position()= $ index,因为我以相反的顺序导航(从最后一个元素到第一个元素)(我发现以相反顺序执行某种foreach的最佳方式)
  • 我需要修复一些错误:(无限循环,如果磁盘部分没有声明分区,则相对于磁盘末端创建唯一的可变大小分区。虽然这有效,但它不是最佳的)

(此处提供的代码(几乎没有变化):https://github.com/finley/SystemImager/blob/initrd-from-imageserver-and-dont-package-initrd/lib/dracut/modules.d/51systemimager/do_partitions.xsl


0
投票

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

  1. 在文档顺序中,可变大小(如果有)之前的主分区/扩展分区
  2. 主要/扩展分区遵循可变大小的分区(如果有),以反向文档顺序
  3. 可变大小的主要/扩展分区(如果有),按文档顺序排列
  4. 为1.但对于逻辑分区
  5. as 2.但是对于逻辑分区
  6. 为3.但是对于逻辑分区

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

#!/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使用的限制。

© www.soinside.com 2019 - 2024. All rights reserved.