如何比较python中的Rpm版本

如何比较python中的Rpm版本,python,regex,rpm,Python,Regex,Rpm,我试图找出如何比较两个RPM列表(当前已安装)和(在本地存储库中可用),并查看哪些RPM已过时。我一直在修补正则表达式,但是RPM有太多不同的命名标准,我无法得到一个好的列表来使用。我的驱动器上没有实际的rpm,所以我不能执行rpm-qif pattern1 = re.compile(r'^([a-zA-Z0-9_\-\+]*)-([a-zA-Z0-9_\.]*)-([a-zA-Z0-9_\.]*)\.(.*)') for rpm in listOfRpms: packageInfo

我试图找出如何比较两个RPM列表(当前已安装)和(在本地存储库中可用),并查看哪些RPM已过时。我一直在修补正则表达式,但是RPM有太多不同的命名标准,我无法得到一个好的列表来使用。我的驱动器上没有实际的rpm,所以我不能执行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)

但是这些都不起作用,比如说,除非我打破一些以前起作用的

  • 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
  • zip-2.3-27
  • zlib-1.2.3-3
  • zlib-1.2.3-32
  • zsh-4.2.6-1

在RPM术语中,
2.el5
是发布字段;2和el5不是单独的字段。然而,正如您的示例所示,发布版不需要包含
。从末尾放下
\.(.*)
,一次捕获释放字段

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

导入rpm
#t1和t2是(版本、发行版)的元组
def比较(t1、t2):
v1,r1=t1
v2,r2=t2
返回rpm.labelCompare(('1',v1,r1),('1',v2,r2))
你问,那额外的
'1'
是什么?这就是epoch,它超越了其他版本比较考虑因素。此外,它通常在文件名中不可用。在这里,为了本练习的目的,我们将其伪造为“1”,但这可能根本不准确。这是两个原因之一,如果您只使用文件名,您的逻辑将关闭

您的逻辑可能与
rpm
不同的另一个原因是
Obsoletes
字段,该字段允许将包升级为具有完全不同名称的包。如果您同意这些限制,请继续

如果您手头没有
rpm
python库,下面是比较
rpm 4.4.2.3
版本、版本和历代的逻辑:

  • 在每个字符串中搜索字母字段
    [a-zA-Z]+
    和数字字段
    [0-9]+
    ,这些字段由垃圾
    [^a-zA-Z0-9]*
    分隔
  • 每个字符串中的连续字段将相互比较
  • 字母部分按字典顺序进行比较,数字部分按数字进行比较
  • 如果一个字段为数字,另一个字段为字母,则数字字段始终被视为较大(较新)
  • 如果一个字符串用完字段,则另一个字符串始终被视为较大(较新)
有关详细信息,请参见RPM源代码中的
lib/rpmvercmp.c

$/

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

然后是架构,这个正则表达式对架构名称没有任何限制,只是不包含“.”

捕获结果为[名称、版本、发布、归档]

欧文回答中关于仅依赖rpm名称的警告仍然适用


现在必须比较版本字符串,这并不简单。我不相信用正则表达式就可以做到这一点。您需要实现比较算法。

RPM有python绑定,可以使用rpmultils.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

下面是一个基于包中
rpmdev vercmp
的工作程序。除了
yum
(它提供了
rpmultils.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)
#/usr/bin/python
导入rpm
导入系统
从rpmultils.miscutils导入stringToVersion
如果len(sys.argv)!=三:
打印“用法:%s”
系统出口(1)
def vercmp((e1、v1、r1)、(e2、v2、r2)):
返回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))
如果rc>0:
打印“%s:%s-%s较新”%(e1、v1、r1)
系统出口(11)
elif rc==0:
打印“这些是相等的”
系统出口(0)
elif rc<0:
打印“%s:%s-%s较新”%(e2、v2、r2)
系统出口(12)

根据Owen的精彩回答,我编写了一个片段,使用系统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)
试试看:
从rpm导入标签比较为\u比较\u rpm\u标签
除恐怖外:
#模拟RPM字段比较
#
#*在每个字符串中搜索字母字段[a-zA-Z]+和
#数字字段[0-9]+以垃圾分隔[^a-zA-Z0-9]*。
#*每个字符串中的连续字段相互比较。
#*字母部分按字典顺序进行比较
#数值部分进行了数值比较。
#*在一个字段为数字,另一个字段为数字的不匹配情况下
#按字母顺序,数字字段始终被视为较大(较新)。
#*如果一个字符串超出o
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)
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))