Ein regulärer Ausdruck für das Parsen von Versionsnummern

82

Ich habe eine Versionsnummer des folgenden Formulars:

version.release.modification

Dabei sind Version, Freigabe und Änderung entweder eine Reihe von Ziffern oder das Platzhalterzeichen '*'. Außerdem kann eine dieser Nummern (und alle vorhergehenden) fehlen.

Folgendes ist also gültig und wird analysiert als:

1.23.456 = version 1, release 23, modification 456
1.23     = version 1, release 23, any modification
1.23.*   = version 1, release 23, any modification
1.*      = version 1, any release, any modification
1        = version 1, any release, any modification
*        = any version, any release, any modification

Diese sind aber nicht gültig:

*.12
*123.1
12*
12.*.34

Kann mir jemand einen nicht allzu komplexen regulären Ausdruck zur Validierung und zum Abrufen der Versions-, Versions- und Änderungsnummern zur Verfügung stellen?

Andrew Borley
quelle
Ich bin mir nicht sicher, ob ein "einfacher" möglich ist.
Svrist

Antworten:

93

Ich würde das Format wie folgt ausdrücken:

"1-3 punktgetrennte Komponenten, jede numerisch, außer dass die letzte * sein kann."

Als regulärer Ausdruck ist das:

^(\d+\.)?(\d+\.)?(\*|\d+)$

[Zum Hinzufügen bearbeiten: Diese Lösung ist eine übersichtliche Methode zur Validierung. Es wurde jedoch darauf hingewiesen, dass das Extrahieren der Werte zusätzliche Arbeit erfordert. Es ist Geschmackssache, ob man damit umgeht, indem man den regulären Ausdruck kompliziert oder die übereinstimmenden Gruppen verarbeitet.

In meiner Lösung erfassen die Gruppen die "."Charaktere. Dies kann mit nicht erfassenden Gruppen wie in Ajborleys Antwort behandelt werden.

Außerdem erfasst die Gruppe ganz rechts die letzte Komponente, selbst wenn weniger als drei Komponenten vorhanden sind. So führt beispielsweise eine Eingabe mit zwei Komponenten dazu, dass die erste und die letzte Gruppe erfasst werden und die mittlere Gruppe undefiniert ist. Ich denke, dies kann von nicht gierigen Gruppen behandelt werden, wenn dies unterstützt wird.

Perl-Code zur Behebung beider Probleme nach dem regulären Ausdruck könnte ungefähr so ​​aussehen:

@version = ();
@groups = ($1, $2, $3);
foreach (@groups) {
    next if !defined;
    s/\.//;
    push @version, $_;
}
($major, $minor, $mod) = (@version, "*", "*");

Was nicht wirklich kürzer ist als das Aufteilen "." ]

Steve Jessop
quelle
1
Das Hinzufügen einiger nicht erfassender Gruppen (siehe meine Antwort unten) bedeutet, dass die erfassenden Gruppen das nachfolgende 'nicht' erfassen. ^ (?: (\ d +) \.)? (?: (\ d +) \.)? (* | \ d +) $ Danke!
Andrew Borley
Das einzige Problem mit diesem - ein sehr netter und sauberer Vorschlag - ist, dass die Gruppen nicht richtig sind, weil 1.2 wegen Gier 1 in der ersten und 2 in der dritten Gruppe erfasst.
jrudolph
39

Verwenden Sie Regex und jetzt haben Sie zwei Probleme. Ich würde das Ding in Punkte (".") Aufteilen und dann sicherstellen, dass jeder Teil entweder ein Platzhalter oder ein Satz von Ziffern ist (Regex ist jetzt perfekt). Wenn das Ding gültig ist, geben Sie einfach den richtigen Teil des Split zurück.

Paweł Hajdan
quelle
11

Dies könnte funktionieren:

^(\*|\d+(\.\d+){0,2}(\.\*)?)$

Auf der obersten Ebene ist "*" ein Sonderfall einer gültigen Versionsnummer. Ansonsten beginnt es mit einer Nummer. Dann gibt es null, eins oder zwei ".nn" -Sequenzen, gefolgt von einem optionalen ". *". Diese Regex würde 1.2.3. * Akzeptieren, was in Ihrer Anwendung möglicherweise zulässig ist oder nicht.

Der Code zum Abrufen der übereinstimmenden Sequenzen, insbesondere des (\.\d+){0,2}Teils, hängt von Ihrer speziellen Regex-Bibliothek ab.

Greg Hewgill
quelle
Gute Antwort! Ich denke, Sie sollten das nicht entkappte * gegen {0,2} tauschen, um eine Übereinstimmung mit 1.2.3.4 zu verhindern. Abhängig von Ihrer regulären Ausdrucksbibliothek möchten Sie das Muster möglicherweise in ^ (<Muster>) $ einschließen, wenn Sie nur eine Suche anstelle einer Übereinstimmung durchführen können.
Dave Webb
Eine geringfügige Änderung von ^ (* | \ d + (\. \ D +) {0,1} (?: (\. *)? | (\. \ D +)?)) $ Würde 1.2.3. * Auch ungültig machen
Pieter
2
Pieter: Ich denke, ich werde aufhören, wo ich jetzt bin. Dies erreicht schnell das Gebiet "Jetzt haben Sie zwei Probleme". :)
Greg Hewgill
11

Vielen Dank für alle Antworten! Das ist Ass :)

Basierend auf der Antwort von OneByOne (die für mich am einfachsten aussah), fügte ich einige nicht erfassende Gruppen hinzu (die '(?:' - Teile - danke von VonC für die Einführung in nicht erfassende Gruppen!), Also die Gruppen, die nur erfassen enthalten die Ziffern oder * Zeichen.

^(?:(\d+)\.)?(?:(\d+)\.)?(\*|\d+)$

Vielen Dank an alle!

Andrew Borley
quelle
1
Könnten Sie dies stattdessen als Bearbeitung zu Ihrer Frage hinzufügen? Auf diese Weise sind die richtigen Antworten in der Nähe der Spitze
svrist
1
Mit Gruppennamen: ^ (?
:(
1
Semversion unterstützen (etwas mehr). - "1.2.3-alpha + abcdedf.lalal" -Match "^ (?: (\ D +) \.)? (?: (\ D +) \.)? (* | \ D +)? (?: \ - ([A-Za-z0-9 \.] +))? (?: \ + ([A-Za-z0-9 \.] +))? $ "
Sam
Beachten Sie, dass bei einer Version, die aus einer einzelnen Nummer besteht, diese von der dritten und (\*|\d+)nicht von der ersten ^(?:(\d+)\.)?Gruppe abgeglichen wird .
Piotr Dobrogost
8

Meine 2 Cent: Ich hatte dieses Szenario: Ich musste Versionsnummern aus einem String-Literal analysieren. (Ich weiß, dass dies sehr anders ist als die ursprüngliche Frage, aber beim Googeln, um einen regulären Ausdruck für das Parsen der Versionsnummer zu finden, wurde dieser Thread oben angezeigt. Fügen Sie diese Antwort hier hinzu.)

Das String-Literal wäre also ungefähr so: "Service-Version 1.2.35.564 wird ausgeführt!"

Ich musste den 1.2.35.564 aus diesem Literal heraus analysieren. Ausgehend von @ajborley lautet mein Regex wie folgt:

(?:(\d+)\.)?(?:(\d+)\.)?(?:(\d+)\.\d+)

Ein kleines C # -Schnipsel zum Testen sieht wie folgt aus:

void Main()
{
    Regex regEx = new Regex(@"(?:(\d+)\.)?(?:(\d+)\.)?(?:(\d+)\.\d+)", RegexOptions.Compiled);

    Match version = regEx.Match("The Service SuperService 2.1.309.0) is Running!");
    version.Value.Dump("Version using RegEx");   // Prints 2.1.309.0        
}
Sudhanshu Mishra
quelle
Ich weiß, dass Sie eine alternative Situation und einen alternativen Fall beschreiben, aber nur um vollständig zu sein: SemVer 'erfordert', dass die Versionszeichenfolge das Format hat X.Y.Z(also genau drei Teile), wobei X und Y nicht negative ganze Zahlen sein müssen und nein zusätzliche führende Nullen. Siehe semver.org .
Jochem Schulenklopper
1
@JochemSchulenklopper danke, ich kenne SemVer, obwohl die Frage nichts über SemVer erwähnt.
Sudhanshu Mishra
1
Wahr. Ich wurde von einem Kollegen über das Parsen von SemVer-Strings auf diese Frage verwiesen, so dass ich die Antworten lesen konnte.
Jochem Schulenklopper
7

Sie wissen nicht, auf welcher Plattform Sie sich befinden, aber in .NET gibt es die System.Version-Klasse, die Versionsnummern von "nnnn" für Sie analysiert.

Duncan Smart
quelle
Nein, es ist seit Version 1.0 da
Duncan Smart
5

Ich stimme eher dem geteilten Vorschlag zu.

Ich habe einen "Tester" für Ihr Problem in Perl erstellt

#!/usr/bin/perl -w


@strings = ( "1.2.3", "1.2.*", "1.*","*" );

%regexp = ( svrist => qr/(?:(\d+)\.(\d+)\.(\d+)|(\d+)\.(\d+)|(\d+))?(?:\.\*)?/,
            onebyone => qr/^(\d+\.)?(\d+\.)?(\*|\d+)$/,
            greg => qr/^(\*|\d+(\.\d+){0,2}(\.\*)?)$/,
            vonc => qr/^((?:\d+(?!\.\*)\.)+)(\d+)?(\.\*)?$|^(\d+)\.\*$|^(\*|\d+)$/,
            ajb => qr/^(?:(\d+)\.)?(?:(\d+)\.)?(\*|\d+)$/,
            jrudolph => qr/^(((\d+)\.)?(\d+)\.)?(\d+|\*)$/
          );

  foreach my $r (keys %regexp){
    my $reg = $regexp{$r};
    print "Using $r regexp\n";
foreach my $s (@strings){
  print "$s : ";

    if ($s =~m/$reg/){
    my ($main, $maj, $min,$rev,$ex1,$ex2,$ex3) = ("any","any","any","any","any","any","any");
    $main = $1 if ($1 && $1 ne "*") ;
    $maj = $2 if ($2 && $2 ne "*") ;
    $min = $3 if ($3 && $3 ne "*") ;
    $rev = $4 if ($4 && $4 ne "*") ;
    $ex1 = $5 if ($5 && $5 ne "*") ;
    $ex2 = $6 if ($6 && $6 ne "*") ;
    $ex3 = $7 if ($7 && $7 ne "*") ;
    print "$main $maj $min $rev $ex1 $ex2 $ex3\n";

  }else{
  print " nomatch\n";
  }
  }
print "------------------------\n";
}

Aktueller Output:

> perl regex.pl
Using onebyone regexp
1.2.3 : 1. 2. 3 any any any any
1.2.* : 1. 2. any any any any any
1.* : 1. any any any any any any
* : any any any any any any any
------------------------
Using svrist regexp
1.2.3 : 1 2 3 any any any any
1.2.* : any any any 1 2 any any
1.* : any any any any any 1 any
* : any any any any any any any
------------------------
Using vonc regexp
1.2.3 : 1.2. 3 any any any any any
1.2.* : 1. 2 .* any any any any
1.* : any any any 1 any any any
* : any any any any any any any
------------------------
Using ajb regexp
1.2.3 : 1 2 3 any any any any
1.2.* : 1 2 any any any any any
1.* : 1 any any any any any any
* : any any any any any any any
------------------------
Using jrudolph regexp
1.2.3 : 1.2. 1. 1 2 3 any any
1.2.* : 1.2. 1. 1 2 any any any
1.* : 1. any any 1 any any any
* : any any any any any any any
------------------------
Using greg regexp
1.2.3 : 1.2.3 .3 any any any any any
1.2.* : 1.2.* .2 .* any any any any
1.* : 1.* any .* any any any any
* : any any any any any any any
------------------------
svrist
quelle
Das wäre schön, da OneByOne am einfachsten aussieht.
Jrudolph
Sie sollten auch die falschen testen. Sie haben es versäumt, die Punkte von OneByOne zu zitieren.
jrudolph
Aktualisiert mit den Punkten und mehr regulären Ausdrücken
svrist
4

Dies sollte für das funktionieren, was Sie festgelegt haben. Es hängt von der Platzhalterposition ab und ist eine verschachtelte Regex:

^((\*)|([0-9]+(\.((\*)|([0-9]+(\.((\*)|([0-9]+)))?)))?))$

http://imgur.com/3E492.png

nomuus
quelle
4

Ich habe viele Antworten gesehen, aber ... ich habe eine neue. Das funktioniert zumindest bei mir. Ich habe eine neue Einschränkung hinzugefügt. Versionsnummern können nicht mit Nullen gefolgt von anderen beginnen (Major, Minor oder Patch).

01.0.0 ist ungültig 1.0.0 ist gültig 10.0.10 ist gültig 1.0.0000 ist ungültig

^(?:(0\\.|([1-9]+\\d*)\\.))+(?:(0\\.|([1-9]+\\d*)\\.))+((0|([1-9]+\\d*)))$

Es basiert auf einem früheren. Aber ich sehe diese Lösung besser ... für mich;)

Genießen!!!

Israel Romero
quelle
3

Noch ein Versuch:

^(((\d+)\.)?(\d+)\.)?(\d+|\*)$

Dies ergibt die drei Teile in Gruppen 4,5,6 ABER: Sie sind nach rechts ausgerichtet. Die erste Nicht-Null-Zahl von 4,5 oder 6 gibt also das Versionsfeld an.

  • 1.2.3 ergibt 1,2,3
  • 1.2. * Ergibt 1,2, *
  • 1.2 ergibt null, 1,2
  • *** gibt null, null, *
  • 1. * gibt null, 1, *
jrudolph
quelle
3
^(?:(\d+)\.)?(?:(\d+)\.)?(\*|\d+)$

Vielleicht könnte eine prägnantere sein:

^(?:(\d+)\.){0,2}(\*|\d+)$

Dies kann dann auf 1.2.3.4.5. * Erweitert oder mit * oder {2} anstelle von {0,2} genau auf XYZ beschränkt werden.

ofaurax
quelle
3

Ich musste nach Versionsnummern suchen / übereinstimmen, die der Maven-Konvention oder sogar nur einer Ziffer entsprechen. Aber auf keinen Fall ein Qualifier. Es war eigenartig, es hat einige Zeit gedauert, bis ich darauf gekommen bin:

'^[0-9][0-9.]*$'

Dies stellt sicher, dass die Version,

  1. Beginnt mit einer Ziffer
  2. Kann eine beliebige Anzahl von Ziffern haben
  3. Nur Ziffern und '.' sind erlaubt

Ein Nachteil ist, dass die Version sogar mit '.' Beenden kann. Aber es kann eine unbestimmte Länge der Version verarbeiten (verrückte Versionierung, wenn Sie es so nennen wollen)

Streichhölzer:

  • 1.2.3
  • 1.09.5
  • 3.4.4.5.7.8.8.
  • 23.6.209.234.3

Wenn Sie mit '.' Nicht unzufrieden sind. Ende, vielleicht können Sie mit Enden mit Logik kombinieren

Shiva
quelle
Um die letzte Ziffer loszuwerden, möchten Sie vielleicht (\d+)(.\d+)*
Folgendes
2
(?ms)^((?:\d+(?!\.\*)\.)+)(\d+)?(\.\*)?$|^(\d+)\.\*$|^(\*|\d+)$

Entspricht genau Ihren 6 ersten Beispielen und lehnt die 4 anderen ab

  • Gruppe 1: major oder major.minor oder '*'
  • Gruppe 2, falls vorhanden: minderjährig oder *
  • Gruppe 3, falls vorhanden: *

Sie können '(? Ms)' entfernen.
Ich habe es verwendet, um anzuzeigen, dass dieser reguläre Ausdruck über QuickRex auf mehrere Zeilen angewendet werden soll

VonC
quelle
2

Dies entspricht auch 1.2.3. *

^ (* | \ d + (. \ d +) {0,2} (. *)?) $

Ich würde das weniger elegante vorschlagen:

(* | \ d + (. \ d +)? (. *)?) | \ d +. \ d +. \ d +)

Sieger
quelle
2

Denken Sie daran, dass reguläre Ausdrücke gierig sind. Wenn Sie also nur innerhalb der Versionsnummer und nicht in einem größeren Text suchen, markieren Sie mit ^ und $ den Anfang und das Ende Ihrer Zeichenfolge. Der reguläre Ausdruck von Greg scheint gut zu funktionieren (habe ihn in meinem Editor nur kurz ausprobiert), aber abhängig von Ihrer Bibliothek / Sprache kann der erste Teil immer noch mit dem "*" innerhalb der falschen Versionsnummern übereinstimmen. Vielleicht fehlt mir etwas, da ich Regexp seit ungefähr einem Jahr nicht mehr verwendet habe.

Dies sollte sicherstellen, dass Sie nur die richtigen Versionsnummern finden können:

^ (\ * | \ d + (\. \ d +) * (\. \ *)?) $

edit: eigentlich hat greg sie schon hinzugefügt und sogar seine lösung verbessert, ich bin zu langsam :)

FrankS
quelle
2

Es scheint ziemlich schwierig zu sein, einen regulären Ausdruck zu haben, der genau das tut, was Sie wollen (dh nur die Fälle akzeptieren, die Sie benötigen, und alle anderen ablehnen und einige Gruppen für die drei Komponenten zurückgeben). Ich habe es ausprobiert und mir Folgendes ausgedacht:

^(\*|(\d+(\.(\d+(\.(\d+|\*))?|\*))?))$

IMO (ich habe nicht ausführlich getestet) sollte dies als Validator für die Eingabe gut funktionieren, aber das Problem ist, dass diese Regex keine Möglichkeit zum Abrufen der Komponenten bietet. Dafür müssen Sie noch einen Split-On-Zeitraum durchführen.

Diese Lösung ist nicht all-in-one, muss aber in der Programmierung meistens nicht. Dies hängt natürlich von anderen Einschränkungen ab, die Sie möglicherweise in Ihrem Code haben.

rslite
quelle
2

Angeben von XSD-Elementen:

<xs:simpleType>
    <xs:restriction base="xs:string">
        <xs:pattern value="[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}(\..*)?"/>
    </xs:restriction>
</xs:simpleType>
Emmerson
quelle
2

Ich nehme das als eine gute Übung - vparse , die eine winzige Quelle hat , mit einer einfachen Funktion:

function parseVersion(v) {
    var m = v.match(/\d*\.|\d+/g) || [];
    v = {
        major: +m[0] || 0,
        minor: +m[1] || 0,
        patch: +m[2] || 0,
        build: +m[3] || 0
    };
    v.isEmpty = !v.major && !v.minor && !v.patch && !v.build;
    v.parsed = [v.major, v.minor, v.patch, v.build];
    v.text = v.parsed.join('.');
    return v;
}
vitaly-t
quelle
1

Noch eine Lösung:

^[1-9][\d]*(.[1-9][\d]*)*(.\*)?|\*$
Oleksandr Yarushevskyi
quelle
1

Zum Parsen von Versionsnummern, die diesen Regeln folgen: - Sind nur Ziffern und Punkte - Kann nicht mit einem Punkt beginnen oder enden - Kann nicht zwei Punkte zusammen sein

Dieser hat mir den Trick angetan.

^(\d+)((\.{1}\d+)*)(\.{0})$

Gültige Fälle sind:

1, 0,1, 1.2.1

Pegpeg
quelle