2010-07-08 18 views
11

Estoy tratando de averiguar cómo puedo comparar 2 listas de RPMS (actualmente instaladas) y (disponibles en el repositorio local) y ver qué RPMS están desactualizados. He estado retocando con expresiones regulares, pero hay tantos estándares de nombres diferentes para RPMS que no puedo obtener una buena lista para trabajar. No tengo el RPMS real en mi disco, así que no puedo hacer rpm -qif.Cómo comparo versiones de RPM en python

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 

Esto funciona para una gran mayoría, pero no todos (2300/2400)

yum-metadata-parser-1.1.2-2.el5 
('yum-metadata-parser', '1.1.2', '2', 'el5') **What I need 

Pero ninguno estos trabajos, por ejemplo, a menos que romper algunos otros que trabajaron antes ..

  • 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
+0

¿cómo está obteniendo la lista de RPM? – Craig

Respuesta

14

En el lenguaje RPM, 2.el5 es el campo de publicación; 2 y el5 no son campos separados. Sin embargo, no es necesario que la versión tenga un ., como muestran los ejemplos. Suelta el \.(.*) desde el final para capturar el campo de lanzamiento de una vez.

Ahora tiene el nombre, la versión y la versión del paquete. La forma más fácil de compararlos es utilizar el módulo de Python rpm:

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)) 

¿Qué tiene ese extra '1', usted pregunta? Eso es época, y anula otras consideraciones de comparación de versión. Además, generalmente no está disponible en el nombre del archivo. Aquí, estamos fingiendo a '1' a los fines de este ejercicio, pero eso puede no ser exacto en absoluto. Esta es una de las dos razones por las que su lógica no funcionará si va solo por nombres de archivo.

La otra razón por la que su lógica puede ser diferente de rpm es el campo Obsoletes, que permite que un paquete se actualice a un paquete con un nombre completamente diferente. Si estás de acuerdo con estas limitaciones, entonces procede.

Si usted no tiene la biblioteca rpm pitón a mano, aquí está la lógica de comparación de cada una de la liberación, la versión y la época como de rpm 4.4.2.3:

  • Buscar cada cadena para campos alfabéticos [a-zA-Z]+ y campos numéricos [0-9]+ separados por basura [^a-zA-Z0-9]*.
  • Los campos sucesivos en cada cadena se comparan entre sí.
  • Las secciones alfabéticas se comparan lexicográficamente y las secciones numéricas se comparan numéricamente.
  • En el caso de un desajuste donde un campo es numérico y uno es alfabético, el campo numérico siempre se considera mayor (más nuevo).
  • En el caso donde una cadena se queda sin campos, la otra siempre se considera mayor (más nueva).

Ver lib/rpmvercmp.c en la fuente de RPM para los detalles sangrientos.

+0

Muchas gracias Owen S. Estaba buscando en el módulo rpm python antes, pero lo descarté porque pensé que solo interactuaba con la base de datos RPM. ¡Funciona de maravilla! Solo tengo nombres de archivo porque estoy sacando una lista de RPM de un servidor zenoss y comparándolo con una lista en un espejo local. Obsoletos no son un requisito para mí. – Adam

+1

¡Gracias por esto!Convertí tu descripción de tu algoritmo en una reserva de Python pura en http://stackoverflow.com/a/42967591/597742 – ncoghlan

0

una expresión regular es mucho más simple /^(.+)-(.+)-(.+).(.+).rpm$/

No estoy al tanto de ningún restricciones a la nombre del paquete (primera captura). Las únicas restricciones en la versión y el lanzamiento son que no contienen '-'. No hay necesidad de codificar esto, ya que los 'no capturados' -se separan esos campos, por lo tanto, si uno tuviera un '-' se dividiría y no sería un único campo, por lo tanto, la captura resultante no contendría un '-' . Solo la primera captura, el nombre, contiene cualquier '-' porque consume todos los '' '' extraños primero.

Luego, está la arquitectura, que esta expresión regular no asume ninguna restricción en el nombre de la arquitectura, excepto que no contiene un '.'.

Los resultados de captura son [nombre, versión, release, arco]

Advertencias de respuesta de Owen acerca de confiar en el nombre rpm solo se siguen aplicando.

0

RPM tiene enlaces de python, lo que le permite utilizar rpmUtils.miscutils.compareEVR. El primer y tercer argumentos de la tupla son el nombre del paquete y la versión del paquete. El medio es la versión. En el siguiente ejemplo, estoy tratando de averiguar dónde se clasifica 3.7.4a.

[[email protected] ~]# 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 
2

He aquí un programa de trabajo con sede fuera de rpmdev-vercmp del paquete rpmdevtools. No debería necesitar nada especial instalado excepto yum (que proporciona el módulo de rpmUtils.miscutils python) para que funcione.

La ventaja sobre las otras respuestas es que no es necesario para analizar nada, simplemente lo alimentan completa RPM cadenas de nombres de versiones como:

$ ./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 

estado de la salida 11 significa que el primero es más reciente, 12 significa que el segundo es más nuevo.

#!/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

Sobre la base de una excelente respuesta de Owen S, que arme un fragmento que utiliza los enlaces RPM sistema si está disponible, pero se cambia por una emulación basada en expresiones regulares de otro modo:

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) 

Nota que refugio ' probé esto extensivamente para mantener la coherencia con la implementación del nivel C: solo lo uso como una implementación alternativa que es al menos lo suficientemente buena como para permitir que el banco de pruebas de Anitya pase en entornos donde los enlaces RPM del sistema no están disponibles.