Как сравнить версии Rpm в python

Я пытаюсь выяснить, как я могу сравнить 2 списка RPMS (в настоящее время установлено) и (доступно в локальном репозитории) и посмотреть, какие RPMS устарели. Я занимаюсь регулярным выражением, но для RPMS существует так много разных стандартов именования, что я не могу получить хороший список для работы. У меня нет реальной RPMS на моем диске, поэтому я не могу сделать rpm -qif.

pattern1 = re.compile(r'^([a-zA-Z0-9_\-\+]*)-([a-zA-Z0-9_\.]*)-([a-zA-Z0-9_\.]*)\.(.*)')
for rpm in listOfRpms:
     packageInfo = pattern1.search(rpm[0]).groups()
     print packageInfo

Это работает для подавляющего большинства, но не для всех (2300/2400)

  yum-metadata-parser-1.1.2-2.el5
('yum-metadata-parser', '1.1.2', '2', 'el5') **What I need

Но никто из них не работает, если я не сломаю некоторые другие, которые работали до этого.

  • WvDial-1.54.0-3
  • Xdelta-1.1.3-20
  • Xdelta-1.1.3-20_2
  • xmlsec1-1.2.6-3
  • xmlsec1-1.2.6-3_2
  • ypbind-1.17.2-13
  • ypbind-1.17.2-8
  • ypserv-2.13-14
  • зип-2.3-27
  • Zlib-1.2.3-3
  • Zlib-1.2.3-3_2
  • ЗШ-4.2.6-1

Ответ 1

В языке RPM 2.el5 - это поле выпуска; 2 и el5 не являются отдельными полями. Однако в релизе не должно быть ., как показывают ваши примеры. Выделите \.(.*) с конца, чтобы захватить поле выпуска одним выстрелом.

Итак, теперь у вас есть имя пакета, версия и релиз. Самый простой способ сравнить их - использовать rpm python module:

import rpm
# t1 and t2 are tuples of (version, release)
def compare(t1, t2):
    v1, r1 = t1
    v2, r2 = t2
    return rpm.labelCompare(('1', v1, r1), ('1', v2, r2))

Что это за лишнее '1', спросите вы? Эта эпоха, и она переопределяет другие варианты сравнения версий. Кроме того, он вообще недоступен в имени файла. Здесь мы подделываем его до "1" для целей этого упражнения, но это может быть не совсем точным. Это одна из двух причин, по которой ваша логика будет отключена, если вы будете искать только имена файлов.

Другая причина, по которой ваша логика может отличаться от rpm, - это поле Obsoletes, которое позволяет обновить пакет до пакета с совершенно другим именем. Если вы в порядке с этими ограничениями, продолжайте.

Если у вас нет библиотеки python rpm, вот логика сравнения каждой версии, версии и эпохи с rpm 4.4.2.3:

  • Поиск каждой строки для буквенных полей [a-zA-Z]+ и числовых полей [0-9]+, разделенных junk [^a-zA-Z0-9]*.
  • Последовательные поля в каждой строке сравниваются друг с другом.
  • Алфавитные разделы сравниваются лексикографически, а числовые разделы сравниваются численно.
  • В случае несоответствия, когда одно поле является числовым, а одно - буквенным, числовое поле всегда считается большим (более новым).
  • В случае, когда одна строка заканчивается полями, другая всегда считается большей (более новая).

См. lib/rpmvercmp.c в источнике RPM для деталей gory.

Ответ 2

Здесь рабочая программа основана на rpmdev-vercmp из пакета rpmdevtools. Вам не нужно ничего специального, но yum (который предоставляет модуль rpmUtils.miscutils python) для его работы.

Преимущество по сравнению с другими ответами - вам не нужно ничего разбирать, просто загрузите все полные строки версии RPM, например:

$ ./rpmcmp.py bash-3.2-32.el5_9.1 bash-3.2-33.el5.1
0:bash-3.2-33.el5.1 is newer
$ echo $?
12

Статус выхода 11 означает, что первый из них более новый, 12 означает, что второй - более новый.

#!/usr/bin/python

import rpm
import sys
from rpmUtils.miscutils import stringToVersion

if len(sys.argv) != 3:
    print "Usage: %s <rpm1> <rpm2>"
    sys.exit(1)

def vercmp((e1, v1, r1), (e2, v2, r2)):
    return rpm.labelCompare((e1, v1, r1), (e2, v2, r2))

(e1, v1, r1) = stringToVersion(sys.argv[1])
(e2, v2, r2) = stringToVersion(sys.argv[2])

rc = vercmp((e1, v1, r1), (e2, v2, r2))
if rc > 0:
    print "%s:%s-%s is newer" % (e1, v1, r1)
    sys.exit(11)

elif rc == 0:
    print "These are equal"
    sys.exit(0)

elif rc < 0:
    print "%s:%s-%s is newer" % (e2, v2, r2)
    sys.exit(12)

Ответ 3

Основываясь на превосходном ответе Owen S, я собрал фрагмент, который использует привязки RPM системы, если он доступен, но возвращается к эмуляции на основе регулярного выражения иначе:

try:
    from rpm import labelCompare as _compare_rpm_labels
except ImportError:
    # Emulate RPM field comparisons
    #
    # * Search each string for alphabetic fields [a-zA-Z]+ and
    #   numeric fields [0-9]+ separated by junk [^a-zA-Z0-9]*.
    # * Successive fields in each string are compared to each other.
    # * Alphabetic sections are compared lexicographically, and the
    #   numeric sections are compared numerically.
    # * In the case of a mismatch where one field is numeric and one is
    #   alphabetic, the numeric field is always considered greater (newer).
    # * In the case where one string runs out of fields, the other is always
    #   considered greater (newer).

    import warnings
    warnings.warn("Failed to import 'rpm', emulating RPM label comparisons")

    try:
        from itertools import zip_longest
    except ImportError:
        from itertools import izip_longest as zip_longest

    _subfield_pattern = re.compile(
        r'(?P<junk>[^a-zA-Z0-9]*)((?P<text>[a-zA-Z]+)|(?P<num>[0-9]+))'
    )

    def _iter_rpm_subfields(field):
        """Yield subfields as 2-tuples that sort in the desired order

        Text subfields are yielded as (0, text_value)
        Numeric subfields are yielded as (1, int_value)
        """
        for subfield in _subfield_pattern.finditer(field):
            text = subfield.group('text')
            if text is not None:
                yield (0, text)
            else:
                yield (1, int(subfield.group('num')))

    def _compare_rpm_field(lhs, rhs):
        # Short circuit for exact matches (including both being None)
        if lhs == rhs:
            return 0
        # Otherwise assume both inputs are strings
        lhs_subfields = _iter_rpm_subfields(lhs)
        rhs_subfields = _iter_rpm_subfields(rhs)
        for lhs_sf, rhs_sf in zip_longest(lhs_subfields, rhs_subfields):
            if lhs_sf == rhs_sf:
                # When both subfields are the same, move to next subfield
                continue
            if lhs_sf is None:
                # Fewer subfields in LHS, so it less than/older than RHS
                return -1
            if rhs_sf is None:
                # More subfields in LHS, so it greater than/newer than RHS
                return 1
            # Found a differing subfield, so it determines the relative order
            return -1 if lhs_sf < rhs_sf else 1
        # No relevant differences found between LHS and RHS
        return 0


    def _compare_rpm_labels(lhs, rhs):
        lhs_epoch, lhs_version, lhs_release = lhs
        rhs_epoch, rhs_version, rhs_release = rhs
        result = _compare_rpm_field(lhs_epoch, rhs_epoch)
        if result:
            return result
        result = _compare_rpm_field(lhs_version, rhs_version)
        if result:
            return result
        return _compare_rpm_field(lhs_release, rhs_release)

Обратите внимание, что я не тестировал это широко для согласованности с реализацией уровня C. Я использую его только как резервную реализацию, которая, по крайней мере, достаточно хороша, чтобы позволить тестовому набору Anitya проходить в средах, где привязки системных RPM недоступны.

Ответ 4

Более простым регулярным выражением является /^(.+)-(.+)-(.+).(.+). rpm $/

Я не знаю никаких ограничений на имя пакета (первый захват). Единственными ограничениями для версии и выпуска являются то, что они не содержат "-" . Нет необходимости кодировать это, так как незахваченные '- отделяют эти поля, поэтому, если у кого-то есть "-" , он будет разделен и не будет ни одной феей, ergo в результате захвата не будет содержать "-" . Только первый захват, имя, содержит любое "-" , потому что он потребляет все посторонние "-" сначала.

Затем существует архитектура, которая не имеет никакого ограничения на имя архитектуры, но это не содержит ".".

Результаты захвата: [имя, версия, релиз, арка]

Предостережения от ответа Оуэна о том, чтобы полагаться только на имя rpm, все еще применяются.

Ответ 5

RPM имеет привязки python, что позволяет использовать rpmUtils.miscutils.compareEVR. Первый и третий аргументы кортежа - это имя пакета и версия упаковки. Середина - это версия. В приведенном ниже примере я пытаюсь выяснить, где сортируется 3.7.4a.

[[email protected] ~]# python
Python 2.4.3 (#1, Dec 10 2010, 17:24:35) 
[GCC 4.1.2 20080704 (Red Hat 4.1.2-50)] on linux2
Type "help", "copyright", "credits" or "license" for more information.
>>> import rpmUtils.miscutils
>>> rpmUtils.miscutils.compareEVR(("foo", "3.7.4", "1"), ("foo", "3.7.4", "1"))
0
>>> rpmUtils.miscutils.compareEVR(("foo", "3.7.4", "1"), ("foo", "3.7.4a", "1")) 
-1
>>> rpmUtils.miscutils.compareEVR(("foo", "3.7.4a", "1"), ("foo", "3.7.4", "1")) 
1