2010-07-08 16 views
11

Sto cercando di scoprire come posso confrontare 2 elenchi di RPMS (attualmente installati) e (Disponibile nel repository locale) e vedere quali RPMS non sono aggiornati. Ho armeggiato con espressioni regolari, ma ci sono così tanti standard di denominazione per RPMS che non riesco a ottenere una buona lista con cui lavorare. Non ho gli RPMS sul mio disco, quindi non posso fare rpm -qif.Come confrontare le versioni Rpm in 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 

Questo funziona per la stragrande maggioranza, ma non tutti (2300/2400)

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

Ma nessuno questi funzionano per esempio a meno che non mi rompere alcuni altri che hanno lavorato prima ..

  • 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

come si ottiene l'elenco di RPM? – Craig

risposta

14

Nel linguaggio RPM, 2.el5 è il campo di rilascio; 2 e el5 non sono campi separati. Tuttavia, il rilascio non deve avere un . in esso come mostrano i tuoi esempi. Rilasciare il \.(.*) dalla fine per acquisire il campo di rilascio in un colpo.

Quindi ora avete un nome, versione e rilascio del pacchetto. Il modo più semplice per confrontarli è quello di utilizzare il modulo python di giri:

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

Cosa c'è quel qualcosa in più '1', vi chiederete? Questa è l'epoca e sovrascrive altre considerazioni sul confronto delle versioni. Inoltre, in genere non è disponibile nel nome del file. Qui, stiamo simulando "1" per gli scopi di questo esercizio, ma potrebbe non essere affatto accurato. Questo è uno dei due motivi per cui la tua logica sarà disattivata se stai usando solo i nomi dei file.

L'altro motivo per cui la logica potrebbe essere diversa da rpm è il campo Obsoletes, che consente di aggiornare un pacchetto a un pacchetto con un nome completamente diverso. Se stai bene con queste limitazioni, quindi procedere.

Se non si dispone di biblioteca rpm pitone a portata di mano, ecco la logica per il confronto ciascuno di rilascio, la versione e epoca come di rpm 4.4.2.3:

  • Cerca ogni stringa per i campi alfabetici [a-zA-Z]+ e campi numerici [0-9]+ separati da spazzatura [^a-zA-Z0-9]*.
  • I campi successivi in ​​ciascuna stringa vengono confrontati tra loro.
  • Le sezioni alfabetiche vengono confrontate lessicograficamente e le sezioni numeriche vengono confrontate numericamente.
  • In caso di disallineamento in cui un campo è numerico e uno alfabetico, il campo numerico viene sempre considerato maggiore (più recente).
  • Nel caso in cui una stringa esaurisca i campi, l'altra viene sempre considerata maggiore (più recente).

Vedere lib/rpmvercmp.c nella sorgente RPM per i dettagli sanguinosi.

+0

Grazie mille Owen S. Stavo esaminando il modulo python rpm in precedenza ma l'ho rimosso perché pensavo che interagisse solo con il database RPM. Funziona come un fascino! Ho solo nomi di file perché sto estraendo un elenco di RPM da un server zenoss e confrontandolo con un elenco su un mirror locale. Gli obsoleti non sono un requisito per me. – Adam

+1

Grazie per questo!Ho convertito la tua descrizione del tuo algoritmo in un fallback di Python puro in http://stackoverflow.com/a/42967591/597742 – ncoghlan

0

Un'espressione regolare è molto più semplice /^(.+)-(.+)-(.+).(.+).rpm$/

Io non sono a conoscenza di eventuali restrizioni alla nome del pacchetto (prima acquisizione). Le uniche restrizioni su versione e rilascio sono che non contengono '-'. Non è necessario codificarlo, poiché i "non catturati" separano quei campi, quindi se uno ha avuto un '-' sarebbe diviso e non essere un singolo feild, quindi la cattura risultante non conterrà un '-' . Solo la prima cattura, il nome, contiene un '-' perché consuma tutto estraneo '-' prima.

Quindi, c'è l'architettura, che questa espressione regolare non presuppone alcuna restrizione sul nome dell'architettura, tranne che non contiene un '.'.

I risultati di cattura sono [nome, versione, release, arch]

Avvertenze da risposta di Owen di fare affidamento sul nome giri da solo si applicano ancora.

0

RPM ha collegamenti Python, che consente di utilizzare rpmUtils.miscutils.compareEVR. Il primo e il terzo argomento della tupla sono il nome del pacchetto e la versione di packaging. Il mezzo è la versione. Nell'esempio seguente, sto cercando di capire dove viene ordinato 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

Ecco un programma di lavoro in base al largo della rpmdev-vercmp dal pacchetto rpmdevtools. Non è necessario alcun dispositivo speciale installato ma yum (che fornisce il modulo python rpmUtils.miscutils) affinché funzioni.

Il vantaggio rispetto alle altre risposte è che non è necessario per analizzare nulla, basta dargli da mangiare piena RPM nome della versione stringhe come:

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

stato uscita 11 significa che il primo è più recente, 12 significa che il secondo è più recente.

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

Sulla base di risposta eccellente di Owen S, ho messo insieme un frammento che utilizza le associazioni del sistema RPM, se disponibile, ma cade di nuovo ad un emulazione basato regex altrimenti:

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 che ho rifugio 't testato questo estensivamente per coerenza con l'implementazione del livello C - Io solo lo uso come un'implementazione fallback che è almeno abbastanza buono da lasciare passare la suite di test di Anitya in ambienti in cui i binding RPM di sistema non sono disponibili.

Problemi correlati