如何在Python中比较RPM版本

12 投票
6 回答
16168 浏览
提问于 2025-04-16 01:00

我正在尝试找出如何比较两个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个)。

  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

6 个回答

2

因为Python的rpm包看起来有点过时,而且在pip里找不到,所以我写了一个小工具,能适用于大多数包的版本,包括处理那些带有~符号的情况。虽然这不能覆盖所有的情况,参考的真实实现可能会更复杂,但对于大多数包来说,这个工具是有效的:

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的规范,应该是反过来的(任何以字母结尾并且最后有一个~的词)

3

这是一个基于 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)
18

在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'是什么?那是epoch,它会覆盖其他版本比较的考虑。此外,通常在文件名中是找不到这个字段的。在这里,我们为了这个练习假装它是'1',但这可能并不准确。这是你仅仅依靠文件名时,逻辑会出错的两个原因之一。

另一个导致你逻辑和rpm不同的原因是Obsoletes字段,它允许一个包升级到一个完全不同名字的包。如果你能接受这些限制,那就继续吧。

如果你手边没有rpm的python库,这里有比较发布、版本和epoch的逻辑,适用于rpm 4.4.2.3

  • 在每个字符串中搜索字母字段[a-zA-Z]+和数字字段[0-9]+,它们之间用一些杂项字符[^a-zA-Z0-9]*分隔。
  • 每个字符串中的相邻字段相互比较。
  • 字母部分按字典顺序比较,数字部分按数值比较。
  • 如果出现不匹配的情况,一个字段是数字,另一个是字母,数字字段总是被认为更大(更新)。
  • 如果一个字符串的字段用完了,另一个字符串总是被认为更大(更新)。

想了解更多细节,可以查看RPM源代码中的lib/rpmvercmp.c

撰写回答