11

我试图找出如何比较 RPMS(当前已安装)和(在本地存储库中可用)的 2 个列表,并查看哪些 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
  • zip-2.3-27
  • zlib-1.2.3-3
  • zlib-1.2.3-3_2
  • zsh-4.2.6-1
4

6 回答 6

16

在 RPM 用语中,2.el5是发布字段;2 和 el5 不是单独的字段。.但是,正如您的示例所示, release 不需要在其中包含。从末端放下\.(.*)以一次性捕获释放场。

所以现在你有了一个包名、版本和发行版。比较它们的最简单方法是使用 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's 不同的另一个原因是该Obsoletes字段,它允许将包升级为具有完全不同名称的包。如果您同意这些限制,请继续。

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

  • 在每个字符串中搜索由 junk 分隔的字母字段[a-zA-Z]+和数字字段。[0-9]+[^a-zA-Z0-9]*
  • 每个字符串中的连续字段相互比较。
  • 字母部分按字典顺序进行比较,数字部分按数字进行比较。
  • 在一个字段是数字而一个是字母的不匹配的情况下,数字字段总是被认为更大(更新)。
  • 在一个字符串用完字段的情况下,另一个总是被认为更大(更新)。

lib/rpmvercmp.c有关血腥细节,请参阅RPM 源代码。

于 2010-07-08T17:51:27.540 回答
3

这是一个基于rpmdevtoolsrpmdev-vercmp的工作程序。您不需要安装任何特殊的东西,但(它提供了python 模块)它可以工作。yumrpmUtils.miscutils

与其他答案相比,优势在于您不需要解析任何内容,只需提供完整的 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)
于 2014-09-25T20:11:24.493 回答
1

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

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

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

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

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

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

于 2011-09-24T06:09:39.303 回答
1

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
于 2012-05-03T22:56:05.847 回答
1

因为 python rpm 包似乎已经过时并且在 pip 中不可用;我写了一个适用于大多数包版本的小实现;~包括标志周围的魔法。这不会覆盖 100% 的实际实现,但它对大多数包都有效:

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 规范,反之亦然(任何以字母结尾的单词,然后是 final ~

于 2019-03-04T14:48:29.430 回答
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 绑定不可用的环境中通过。

于 2017-03-23T05:01:32.927 回答