Wyrażenie regularne do analizowania numeru wersji

84

Mam numer wersji w następującym formularzu:

version.release.modification

gdzie wersja, wydanie i modyfikacja to zestaw cyfr lub znak wieloznaczny „*”. Ponadto może brakować któregokolwiek z tych numerów (i wszystkich poprzedzających).

Więc poniższe są prawidłowe i analizowane jako:

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

Ale te nie są ważne:

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

Czy ktoś może podać mi niezbyt skomplikowane wyrażenie regularne, aby zweryfikować i pobrać numery wydania, wersji i modyfikacji?

Andrew Borley
źródło
Nie jestem pewien, czy „prosty” jest możliwy.
svrist

Odpowiedzi:

97

Format bym wyraził jako:

„Od 1 do 3 elementów rozdzielonych kropkami, każdy numeryczny, z wyjątkiem tego, że ostatni może być *”

Jako wyrażenie regularne to:

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

[Edytuj, aby dodać: to rozwiązanie jest zwięzłym sposobem walidacji, ale zwrócono uwagę, że wyodrębnianie wartości wymaga dodatkowej pracy. To kwestia gustu, czy poradzić sobie z tym, komplikując wyrażenie regularne, czy przetwarzając dopasowane grupy.

W moim rozwiązaniu grupy chwytają "."postacie. Można sobie z tym poradzić przy użyciu grup bez przechwytywania, jak w odpowiedzi Ajborleya.

Ponadto grupa znajdująca się najbardziej po prawej stronie przechwyci ostatni komponent, nawet jeśli jest mniej niż trzy komponenty, więc na przykład dwuskładnikowe wejście skutkuje przechwyceniem pierwszej i ostatniej grupy, a środkowej niezdefiniowanej. Myślę, że mogą sobie z tym poradzić niechciane grupy, jeśli są wspierane.

Kod Perla rozwiązujący oba problemy po wyrażeniu regularnym mógłby wyglądać mniej więcej tak:

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

Co tak naprawdę nie jest krótsze niż rozdzielenie się "." ]

Steve Jessop
źródło
1
Dodanie kilku nieprzechwytywanych grup (patrz moja odpowiedź poniżej) oznacza, że ​​grupy przechwytujące nie przechwytują końcowego znaku „”. ^ (?: (\ d +) \.)? (?: (\ d +) \.)? (* | \ d +) $ Dzięki!
Andrew Borley,
Jedyny problem z tą propozycją - jest bardzo ładną i czystą propozycją - polega na tym, że grupy nie mają racji, ponieważ 1.2 zdobędzie 1 w pierwszej i 2 w trzeciej grupie z powodu chciwości.
jrudolph
39

Użyj wyrażenia regularnego, a teraz masz dwa problemy. Podzieliłbym rzecz na kropki („.”), A następnie upewniłem się, że każda część jest albo symbolem wieloznacznym, albo zestawem cyfr (wyrażenie regularne jest teraz doskonałe). Jeśli coś jest poprawne, po prostu zwracasz poprawny fragment podziału.

Paweł Hajdan
źródło
11

To może zadziałać:

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

Na najwyższym poziomie „*” jest specjalnym przypadkiem prawidłowego numeru wersji. W przeciwnym razie zaczyna się od liczby. Następnie jest zero, jedna lub dwie sekwencje „.nn”, po których następuje opcjonalny „. *”. To wyrażenie regularne akceptuje 1.2.3. *, Co może być dozwolone w Twojej aplikacji lub nie.

Kod do pobierania dopasowanych sekwencji, zwłaszcza (\.\d+){0,2}części, będzie zależał od konkretnej biblioteki wyrażeń regularnych.

Greg Hewgill
źródło
Świetna odpowiedź! Myślę, że powinieneś zamienić niezmienione * na {0,2}, aby zapobiec dopasowaniu 1.2.3.4. W zależności od biblioteki wyrażeń regularnych możesz chcieć zawrzeć wzorzec w ^ (<wzorzec>) $, jeśli możesz tylko wyszukiwać, a nie dopasowywać.
Dave Webb,
Nieznaczna zmiana w ^ (* | \ d + (\. \ D +) {0,1} (?: (\. *)? | (\. \ D +)?)) $ Również unieważni 1.2.3. *
Pieter
2
Pieter: Myślę, że na razie zatrzymam się tam, gdzie jestem. To szybko wkracza na obszar „teraz masz dwa problemy”. :)
Greg Hewgill,
11

Dzięki za wszystkie odpowiedzi! To jest as :)

W oparciu o odpowiedź OneByOne (która wydawała mi się najprostsza) dodałem kilka grup nieprzechwytywanych (części „(?:” - dzięki VonC za wprowadzenie mnie do grup nieprzechwytujących!), Więc grupy, które przechwytują zawierać cyfry lub znak *.

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

Wiele dzięki wszystkim!

Andrew Borley
źródło
1
Czy możesz zamiast tego dodać to jako zmianę do swojego pytania? W ten sposób właściwe odpowiedzi są blisko szczytu
svrist
1
Z nazwami grup: ^ (? :(? <major> \ d +) \.)? (?
:(
1
obsługuje semwersję (trochę więcej). - "1.2.3-alpha + abcdedf.lalal" -match "^ (?: (\ D +) \.)? (?: (\ D +) \.)? (* | \ D +)? (?: \ - ([A-Za-z0-9 \.] +))? (?: \ + ([A-Za-z0-9 \.] +))? $ "
Sam,
Pamiętaj, że w przypadku wersji składającej się z jednego numeru zostanie dopasowana trzecia, a (\*|\d+)nie pierwsza ^(?:(\d+)\.)?grupa.
Piotr Dobrogost
8

Moje 2 centy: miałem taki scenariusz: musiałem przeanalizować numery wersji z literału ciągu. (Wiem, że to bardzo różni się od pierwotnego pytania, ale wyszukiwanie wyrażenia regularnego w celu przeanalizowania numeru wersji pokazało ten wątek u góry, więc dodałem tę odpowiedź tutaj)

Tak więc literał ciągu wyglądałby tak: „Usługa w wersji 1.2.35.564 jest uruchomiona!”

Musiałem przeanalizować 1.2.35.564 z tego dosłownego. Biorąc wskazówkę z @ajborley, moje wyrażenie regularne wygląda następująco:

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

Mały fragment kodu C # do przetestowania wygląda jak poniżej:

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
źródło
Wiem, że opisujesz alternatywną sytuację i przypadek, ale tylko po to, by być kompletnym: SemVer „wymaga”, aby ciąg wersji miał format X.Y.Z(czyli dokładnie trzy części), gdzie X i Y muszą być nieujemnymi liczbami całkowitymi i nie dodatkowe zera wiodące. Zobacz semver.org .
Jochem Schulenklopper
1
@JochemSchulenklopper dzięki, znam SemVer, chociaż w pytaniu nic nie wspomina o SemVer.
Sudhanshu Mishra
1
Prawdziwe. Kolega odesłał mnie do tego pytania dotyczącego analizowania ciągów SemVer, co ułożyło moją interpretację odpowiedzi.
Jochem Schulenklopper
7

Nie wiem, na jakiej platformie jesteś, ale w .NET jest klasa System.Version, która przeanalizuje dla Ciebie numery wersji „nnnn”.

Duncan Smart
źródło
Nie, istnieje od wersji 1.0
Duncan Smart
5

Zgadzam się z podzieloną sugestią.

Stworzyłem „testera” dla twojego problemu w perlu

#!/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";
}

Wyjście prądowe:

> 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
źródło
Byłoby fajnie, ponieważ OneByOne wygląda na najprostszy.
jrudolph
Powinieneś także przetestować niewłaściwe. Brakowało Ci zacytowania kropek OneByOne.
jrudolph
Aktualizacja za pomocą kropek i więcej
wyrażeń
4

To powinno działać zgodnie z ustaleniami. Opiera się na pozycji dzikiej karty i jest zagnieżdżonym wyrażeniem regularnym:

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

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

nomuus
źródło
4

Widziałem wiele odpowiedzi, ale ... mam nową. Przynajmniej na mnie działa. Dodałem nowe ograniczenie. Numery wersji nie mogą zaczynać się od zer, po których następują inne.

01.0.0 jest niepoprawne 1.0.0 jest poprawne 10.0.10 jest poprawne 1.0.0000 jest niepoprawne

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

Jest oparty na poprzednim. Ale widzę to rozwiązanie lepiej ... dla mnie;)

Cieszyć się!!!

Israel Romero
źródło
3

Kolejna próba:

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

To daje trzy części w grupach 4,5,6 ALE: Są wyrównane do prawej strony. Zatem pierwsza niezerowa wartość 4,5 lub 6 daje pole wersji.

  • 1.2.3 daje 1,2,3
  • 1.2. * Daje 1,2, *
  • 1.2 daje null, 1,2
  • *** daje null, null, *
  • 1. * daje null, 1, *
jrudolph
źródło
3
^(?:(\d+)\.)?(?:(\d+)\.)?(\*|\d+)$

Być może bardziej zwięzły mógłby być:

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

Można to następnie ulepszyć do 1.2.3.4.5. * Lub ograniczyć dokładnie do XYZ za pomocą * lub {2} zamiast {0,2}

ofaurax
źródło
3

Miałem wymóg wyszukiwania / dopasowywania numerów wersji, zgodnie z konwencją maven lub nawet pojedynczej cyfry. Ale w żadnym wypadku nie ma kwalifikatora. To było dziwne, zajęło mi trochę czasu, a potem wymyśliłem to:

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

Daje to pewność, że wersja

  1. Rozpoczyna się cyfrą
  2. Może mieć dowolną liczbę cyfr
  3. Tylko cyfry i „.” są dozwolone

Jedną z wad jest to, że wersja może nawet kończyć się na „.” Ale może obsłużyć nieokreśloną długość wersji (szalone wersjonowanie, jeśli chcesz to tak nazwać)

Mecze:

  • 1.2.3
  • 1.09.5
  • 3.4.4.5.7.8.8.
  • 23.6.209.234.3

Jeśli nie jesteś niezadowolony z „.” kończąc, może można połączyć z końcami z logiką

siedmiodniowa żałoba
źródło
Aby pozbyć się ostatniej cyfry, może chciałbyś spróbować tego:(\d+)(.\d+)*
cassioso
2
(?ms)^((?:\d+(?!\.\*)\.)+)(\d+)?(\.\*)?$|^(\d+)\.\*$|^(\*|\d+)$

Dokładnie pasuje do 6 pierwszych przykładów i odrzuca 4 pozostałe

  • grupa 1: major lub major. minor lub '*'
  • grupa 2, jeśli istnieje: małoletni lub *
  • grupa 3 jeśli istnieje: *

Możesz usunąć '(? Ms)'
Użyłem go do wskazania tego wyrażenia regularnego, które ma być zastosowane w wielu wierszach za pomocą QuickRex

VonC
źródło
2

To pasuje również do 1.2.3. *

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

Proponuję mniej eleganckie:

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

Zwycięzca
źródło
2

Pamiętaj, że wyrażenia regularne są chciwe, więc jeśli szukasz tylko w ciągu numeru wersji, a nie w większym tekście, użyj ^ i $, aby zaznaczyć początek i koniec łańcucha. Wyrażenie regularne od Grega wydaje się działać dobrze (po prostu wypróbowałem je w moim edytorze), ale w zależności od biblioteki / języka pierwsza część może nadal dopasować „*” w niewłaściwych numerach wersji. Może czegoś mi brakuje, ponieważ nie używałem Regexp od około roku.

Powinno to zapewnić, że możesz znaleźć tylko prawidłowe numery wersji:

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

edycja: właściwie greg już je dodał i nawet poprawił swoje rozwiązanie, jestem za wolny :)

FrankS
źródło
2

Wydaje się dość trudne, aby mieć wyrażenie regularne, które robi dokładnie to, czego chcesz (tj. Akceptuje tylko te przypadki, których potrzebujesz, a wszystkie inne odrzuca i zwraca niektóre grupy dla trzech składników). Spróbowałem i wymyśliłem to:

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

IMO (nie testowałem szczegółowo) to powinno działać dobrze jako walidator dla danych wejściowych, ale problem polega na tym, że to wyrażenie regularne nie oferuje sposobu na odzyskanie komponentów. W tym celu nadal musisz zrobić podział na okres.

To rozwiązanie nie jest kompleksowe, ale w większości przypadków w programowaniu nie musi. Oczywiście zależy to od innych ograniczeń, które możesz mieć w swoim kodzie.

rslite
źródło
2

Określanie elementów XSD:

<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
źródło
2

Przyjmuję to jako dobre ćwiczenie - vparse , które ma maleńkie źródło , z prostą funkcją:

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
źródło
2

W przypadku analizowania numerów wersji, które są zgodne z następującymi regułami: - są tylko cyframi i kropkami - nie mogą zaczynać się ani kończyć kropką - nie mogą składać się z dwóch kropek

Ten mnie załatwił.

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

Prawidłowe przypadki to:

1, 0,1, 1.2.1

pegpeg
źródło
1

Jeszcze jedno rozwiązanie:

^[1-9][\d]*(.[1-9][\d]*)*(.\*)?|\*$
Oleksandr Yarushevskyi
źródło
1

Czasami numery wersji mogą zawierać pomniejsze informacje alfanumeryczne (np. 1.2.0b lub 1.2.0-beta ). W tym przypadku używam tego wyrażenia regularnego:

([0-9]{1,4}(\.[0-9a-z]{1,6}){1,5})
Marc Ruef
źródło
1

Znalazłem to i działa dla mnie:

/(\^|\~?)(\d|x|\*)+\.(\d|x|\*)+\.(\d|x|\*)+
OrAssayag
źródło