如何在Python中比较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 = 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 个回答
因为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的规范,应该是反过来的(任何以字母结尾并且最后有一个~
的词)
这是一个基于 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)
在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
。