использование xmlstarlet для разбора списка в определенном порядке - PullRequest
0 голосов
/ 28 февраля 2019

Я пытаюсь использовать 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 в приведенном выше случае), затем он создается с полным оставшимся пространством, не оставляя места на диске для обработки последних.Таким образом, например, невозможно установить первичныйПоменяйте местами раздел в конце диска с разделом /, который имеет переменный размер.

Q: Есть ли умный способ использовать xmlstarlet sel для вывода разделов в следующем порядке:

listвсе первичные и расширенные разделы в том же порядке, в каком они записаны в xml-файле, до тех пор, пока не будет виден раздел с размером "*";

  • помните об этом разделе с переменным размером
  • , затем перечислитезавершите в обратном порядке другие разделы
  • и, наконец, напечатайте раздел переменного размера
  • повторите операцию с логическими разделами, если таковые имеются

Для всех разделов добавьте полеуказав, перечислены ли они в порядке или в обратном порядке, чтобы я мог знать, должен ли я создавать раздел относительно начала свободного пространства или относительно конца свободного пространства.(переменный раздел будет помечен в обычном порядке, как если бы он создавался, начиная с начала свободного места)

Для приведенного примера (disk-layout-complex.xml) в этом списке будут перечислены разделы, которые будут созданы в следующемorder: (ниже вывод 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, нет языка, который нуждается в библиотеках в целом.

Я довольно убежден, что есть решение, которое выполняет большую часть работы, если не все, но я далеко не достаточно квалифицирован, чтобы дажеоцените, можно ли это сделать таким образом.Я думаю, что могу добиться этого в чистом виде, но это будет гораздо менее элегантно.

Ответы [ 4 ]

0 голосов
/ 09 марта 2019

У меня был другой взгляд на это.Указанный вами список разделен на 2 на 3 группы:

  1. первичные / расширенные разделы, предшествующие единице переменного размера (если есть), в порядке документа
  2. первичные / расширенные разделы, следующие запеременный размер (если есть), в обратном порядке документов
  3. основной / расширенный раздел переменного размера (если есть), в порядке документов
  4. как 1. но для логических разделов
  5. как 2. но для логических разделов
  6. как 3. но для логического раздела

Вот сложная команда xmlstarlet с 6 тестами узлов XPath и некоторыми переменными оболочки (будьте осторожныс цитатой) вставлен, чтобы сократить повторение.Дополнительный предикат - [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.

0 голосов
/ 07 марта 2019

Окончательный ответ следующий:

<!— 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 уродлив, но я не знаю, как придать ему более сексуальный вид
  • содержит ($ type, part [position ()= $ index] / @ p_type) используется для проверки соответствия $ type (тип раздела) (test = "@ p_type = $ type" с $ type, содержащим «primary | extended» или «logic»)
  • Я должен был использовать position () = $ index, когда я перемещаюсь в обратном порядке (от последнего элемента к 1-му элементу) (лучший способ, с помощью которого я выполняю сортировку foreach в обратном порядке)
  • Мне нужно исправитьнесколько ошибок: (бесконечный цикл, если в разделе диска не объявлен раздел, создается уникальный раздел с переменным размером относительно конца диска. Хотя это работает, это не оптимально)

(коддоступно здесь (с небольшими изменениями): https://github.com/finley/SystemImager/blob/initrd-from-imageserver-and-dont-package-initrd/lib/dracut/modules.d/51systemimager/do_partitions.xsl)

0 голосов
/ 08 марта 2019

Если я правильно понимаю, порядок сортировки, который вы хотите, может быть достигнут следующим образом (код сокращен для ясности):

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 голосов
/ 04 марта 2019

После тщательного поиска в Google и изучения основ 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'

Я не уверен в рекурсивной форме алгоритма, но что нужно создать впорядок: - список первичных / расширенных разделов, создаваемых с начала диска; - список первичных / расширенных разделов, создаваемых с конца диска; - первичный / расширенный раздел с размером = ""; - список логических разделов длясоздать с начала расширенного раздела - список логических разделов для создания с конца расширенного раздела - логический раздел, имеющий размер ""

Будучи новичком в 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 сканированиях (один для основного / расширенного разделов и для логических разделов) ...

Работабез переменных это как-то непросто.

...