我如何比较python中的Rpm版本

问题描述 投票:11回答:6

我试图找出如何比较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
  • zsh的-4.2.6-1
python regex rpm
6个回答
15
投票

在RPM的说法中,2.el5是释放领域; 2和el5不是单独的字段。但是,正如您的示例所示,释放不需要有.。从最后放下\.(.*)以一次捕获释放区域。

所以现在你有了包名,版本和发行版。比较它们的最简单方法是使用rpm的python模块:

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字段,它允许将包升级到具有完全不同名称的包。如果您对这些限制没问题,请继续。

如果您手头没有rpm python库,那么这里是比较rpm 4.4.2.3的每个版本,版本和纪元的逻辑:

  • 在每个字符串中搜索由垃圾[a-zA-Z]+分隔的字母字段[0-9]+和数字字段[^a-zA-Z0-9]*
  • 将每个字符串中的连续字段相互比较。
  • 按字典顺序比较按字母顺序排列的部分,并以数字方式比较数字部分。
  • 如果不匹配,其中一个字段是数字,一个是字母,则数字字段始终被视为更大(更新)。
  • 在一个字符串用完字段的情况下,另一个字符串总是被认为更大(更新)。

有关详细信息,请参阅RPM源代码中的lib/rpmvercmp.c


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)

1
投票

基于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's less than/older than RHS
                return -1
            if rhs_sf is None:
                # More subfields in LHS, so it's 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绑定不可用的环境中通过。


1
投票

更简单的正则表达式是/^(.+)-(.+)-(.+)\.(.+)\.rpm$/

我不知道对包名的任何限制(第一次捕获)。对版本和发行版的唯一限制是它们不包含“ - ”。没有必要对此进行编码,因为未捕获的' - 将这些字段分开,因此如果确实有' - '它将被拆分而不是单个字段,因此产生的捕获不会包含' - ' 。只有第一个捕获名称包含任何“ - ”,因为它首先消耗所有无关的“ - ”。

然后,有一个体系结构,这个正则表达式假设没有对体系结构名称的限制,除了它不包含'。'。

捕获结果是[name,version,release,arch]

Owen关于单独依赖rpm名称的回答仍然适用。

现在你必须比较版本字符串,这不是直截了当的。我不相信可以用正则表达式完成。您需要实施比较算法。


0
投票

RPM具有python绑定,允许您使用rpmUtils.miscutils.compareEVR。元组的第一个和第三个参数是包名称和包装版本。中间是版本。在下面的例子中,我试图弄清楚3.7.4a的排序方式。

[root@rhel56 ~]# 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

0
投票

因为python rpm包似乎已经过时了,并且在pip中不可用;我写了一个适用于大多数软件包版本的小实现;包括~标志周围的魔力。这不会涵盖100%的real implementation,但它可以解决大多数包:

def rpm_sort(elements):
    """ sort list elements using 'natural sorting': 1.10 > 1.9 etc...
        taking into account special characters for rpm (~) """

    alphabet = "~0123456789abcdefghijklmnopqrstuvwxyz-."

    def convert(text):
        return [int(text)] if text.isdigit() else ([alphabet.index(letter) for letter in text.lower()] if text else [1])

    def alphanum_key(key):
        return [convert(c) for c in re.split('([0-9]+)', key)]
    return sorted(elements, key=alphanum_key)

测试:

rpms = ['my-package-0.2.1-0.dev.20180810',
        'my-package-0.2.2-0~.dev.20181011',
        'my-package-0.2.2-0~.dev.20181012',
        'my-package-0.2.2-0',
        'my-package-0.2.2-0.dev.20181217']
self.assertEqual(rpms, rpm_sort(rpms))

不包括

目前只有一个案例,我知道没有被覆盖,但其他一些可能会弹出:word~> word,而根据rpm规范,逆应该是真的(任何单词以字母结尾,然后是最后的~

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