Регулярное выражение для разбора номера версии

86

У меня есть номер версии следующего вида:

version.release.modification

где версия, выпуск и модификация - это либо набор цифр, либо подстановочный знак '*'. Кроме того, любой из этих номеров (и любых предшествующих) может отсутствовать.

Итак, следующие действительны и разбираются как:

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

Но это недействительно:

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

Может ли кто-нибудь предоставить мне не слишком сложное регулярное выражение для проверки и получения номеров выпуска, версии и модификации?

Эндрю Борли
источник
Я не уверен, что «простой» возможен.
svrist

Ответы:

98

Я бы выразил формат как:

"1-3 компонента, разделенных точками, каждая цифра, за исключением последнего, может быть *"

В качестве регулярного выражения это:

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

[Отредактируйте, чтобы добавить: это решение - краткий способ проверки, но было указано, что извлечение значений требует дополнительной работы. Решать ли это за счет усложнения регулярного выражения или за счет обработки согласованных групп - дело вкуса.

В моем решении группы захватывают "." персонажей. С этим можно справиться с использованием групп без захвата, как в ответе ajborley.

Кроме того, самая правая группа захватит последний компонент, даже если их меньше трех, и поэтому, например, двухкомпонентный ввод приводит к захвату первой и последней групп, а средний - неопределенному. Я думаю, что с этим могут справиться не жадные группы, если они поддерживаются.

Код Perl для решения обеих проблем после регулярного выражения может быть примерно таким:

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

Что на самом деле не короче, чем разделение на "." ]

Стив Джессоп
источник
1
Добавление некоторых групп без захвата (см. Мой ответ ниже) означает, что группы захвата не захватывают замыкающую '.' ^ (?: (\ d +) \.)? (?: (\ d +) \.)? (* | \ d +) $ Спасибо!
Эндрю Борли
Единственная проблема с этим предложением - очень красивым и чистым - состоит в том, что группы не правы, потому что 1.2 из-за жадности захватит 1 в первой и 2 в третьей группе.
jrudolph
40

Используйте регулярное выражение, и теперь у вас есть две проблемы. Я бы разделил это на точки («.»), А затем убедился, что каждая часть представляет собой либо подстановочный знак, либо набор цифр (регулярное выражение сейчас идеально). Если это действительно так, вы просто возвращаете правильный кусок разбиения.

Павел Хайдан
источник
12

Это может сработать:

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

На верхнем уровне «*» - это особый случай допустимого номера версии. В противном случае он начинается с числа. Затем идет ноль, одна или две последовательности «.nn», за которыми следует необязательный «. *». Это регулярное выражение будет принимать 1.2.3. *, Что может быть или не разрешено в вашем приложении.

Код для получения согласованных последовательностей, особенно (\.\d+){0,2}части, будет зависеть от вашей конкретной библиотеки регулярных выражений.

Грег Хьюгилл
источник
Отличный ответ! Я думаю, вам следует заменить неэкранированный * на {0,2}, чтобы предотвратить совпадение 1.2.3.4. В зависимости от вашей библиотеки регулярных выражений вы можете заключить шаблон в ^ (<pattern>) $, если вы можете выполнять только поиск, а не совпадение.
Дэйв Уэбб,
Небольшое изменение в ^ (* | \ d + (\. \ D +) {0,1} (?: (\. *)? | (\. \ D +)?)) $ Сделало бы 1.2.3. * Тоже недействительным
Питер
2
Питер: Думаю, я сейчас остановлюсь на своем. Это быстро входит в зону «теперь у вас есть две проблемы». :)
Грег Хьюгилл
12

Спасибо за все ответы! Это туз :)

Основываясь на ответе OneByOne (который мне показался наиболее простым), я добавил несколько групп без захвата (части '(?:' - спасибо VonC за представление меня в группы без захвата!), Так что группы, которые захватывают только содержат цифры или символ *.

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

Всем большое спасибо!

Эндрю Борли
источник
1
Не могли бы вы вместо этого добавить это как правку к вашему вопросу? Таким образом, правильные ответы близки к вершине
svrist
1
С именами групп: ^ (? :(? <major> \ d +) \.)? (?
:(
1
поддержка семверсии (немного больше). - "1.2.3-alpha + abcdedf.lalal" -match "^ (?: (\ D +) \.)? (? (?: (\ D +) \.)? (* | \ D +)? (?: \ - ([A-Za-z0-9 \.] +))? (?: \ + ([A-Za-z0-9 \.] +))? $ "
Сэм
Помните, что в случае версии, состоящей из одного номера, ей будет соответствовать третья, а (\*|\d+)не первая ^(?:(\d+)\.)?группа.
Петр Доброгост
9

Мои 2 цента: у меня был такой сценарий: мне приходилось разбирать номера версий из строкового литерала. (Я знаю, что это очень отличается от исходного вопроса, но поиск в Google, чтобы найти регулярное выражение для анализа номера версии, показал этот поток вверху, поэтому добавив этот ответ здесь)

Таким образом, строковый литерал будет выглядеть примерно так: «Служба версии 1.2.35.564 работает!»

Мне пришлось разобрать 1.2.35.564 из этого литерала. Взяв реплику от @ajborley, мое регулярное выражение выглядит следующим образом:

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

Небольшой фрагмент кода C # для проверки выглядит следующим образом:

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        
}
Судханшу Мишра
источник
Я знаю, что вы описываете альтернативную ситуацию и случай, но для полноты: SemVer 'требует', чтобы строка версии имела формат X.Y.Z( т.е. ровно три части), где X и Y должны быть неотрицательными целыми числами, а не дополнительные ведущие нули. См. Semver.org .
Jochem Schulenklopper
1
@JochemSchulenklopper спасибо, я знаю о SemVer, хотя в вопросе ничего не упоминается о SemVer.
Судханшу Мишра
1
Правда. Коллега направил меня на этот вопрос о синтаксическом анализе строк SemVer, так что я прочитал ответы на эти вопросы.
Jochem Schulenklopper
8

Не знаю, на какой платформе вы работаете, но в .NET есть класс System.Version, который будет анализировать номера версий «nnnn» за вас.

Дункан Смарт
источник
Нет, это было с версии 1.0
Дункан Смарт
6

Я склонен согласиться с раздельным предложением.

Я создал "тестера" для вашей проблемы на perl

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

Токовый выход:

> 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
------------------------
сврист
источник
Это было бы неплохо, поскольку OneByOne выглядит самым простым.
jrudolph
Вы также должны проверить неправильные. Вы пропустили процитировать точки OneByOne.
jrudolph
Обновлено с точками и другими
регулярными выражениями
5
^(?:(\d+)\.)?(?:(\d+)\.)?(\*|\d+)$

Возможно, более лаконичным было бы:

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

Затем его можно расширить до 1.2.3.4.5. * Или ограничить точно до XYZ, используя * или {2} вместо {0,2}

офауракс
источник
5

Это должно работать для того, что вы оговорили. Он зависит от позиции подстановочного знака и представляет собой вложенное регулярное выражение:

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

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

номуус
источник
5

Я видел много ответов, но ... у меня есть новый. По крайней мере, у меня это работает. Я добавил новое ограничение. Номера версий (старшие, второстепенные или патч) не могут начинаться с нуля, за которым следуют другие.

01.0.0 недействительно 1.0.0 допустимо 10.0.10 допустимо 1.0.0000 недействительно

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

Он основан на предыдущем. Но я лучше вижу это решение ... для меня;)

Наслаждаться!!!

Израиль Ромеро
источник
4

Еще одна попытка:

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

Это дает три части в группах 4,5,6 НО: Они выровнены по правому краю. Таким образом, первое ненулевое значение из 4,5 или 6 дает поле версии.

  • 1.2.3 дает 1,2,3
  • 1.2. * Дает 1,2, *
  • 1.2 дает ноль, 1,2
  • *** дает null, null, *
  • 1. * дает null, 1, *
Джрудольф
источник
4

У меня было требование искать / сопоставлять номера версий в соответствии с соглашением maven или даже с одной цифрой. Но не в любом случае. Это было странно, мне потребовалось время, и я придумал это:

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

Это гарантирует, что версия,

  1. Начинается с цифры
  2. Может иметь любое количество цифр
  3. Только цифры и '.' разрешается

Одним из недостатков является то, что версия может даже заканчиваться на '.' Но он может обрабатывать неограниченную длину версии (сумасшедшее управление версиями, если вы хотите это так называть)

Матчи:

  • 1.2.3
  • 1.09.5
  • 3.4.4.5.7.8.8.
  • 23.6.209.234.3

Если вы не недовольны "." окончание, может быть, вы можете объединить концы с логикой

Шива
источник
Чтобы избавиться от последней цифры, возможно, вы захотите попробовать следующее:(\d+)(.\d+)*
cassioso
3
(?ms)^((?:\d+(?!\.\*)\.)+)(\d+)?(\.\*)?$|^(\d+)\.\*$|^(\*|\d+)$

Точно соответствует вашим 6 первым примерам и отвергает 4 других

  • группа 1: major или major.minor или '*'
  • группа 2, если существует: несовершеннолетняя или *
  • группа 3, если существует: *

Вы можете удалить '(? Ms)'.
Я использовал его, чтобы указать, что это регулярное выражение будет применяться к многострочным через QuickRex

VonC
источник
3

Это тоже соответствует 1.2.3. *

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

Я бы предложил менее элегантный:

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

Виктор
источник
3

Имейте в виду, что регулярные выражения являются жадными, поэтому, если вы просто ищете в строке номера версии, а не в большом тексте, используйте ^ и $, чтобы отметить начало и конец вашей строки. Регулярное выражение от Грега, похоже, работает нормально (просто попробовал его в моем редакторе), но в зависимости от вашей библиотеки / языка первая часть может соответствовать «*» в неправильных номерах версий. Возможно, мне что-то не хватает, потому что я не использовал Regexp около года.

Это должно гарантировать, что вы сможете найти только правильные номера версий:

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

edit: на самом деле Грег уже добавил их и даже улучшил свое решение, я слишком медленный :)

FrankS
источник
3

Кажется, довольно сложно иметь регулярное выражение, которое делает именно то, что вы хотите (т. Е. Принимает только те случаи, которые вам нужны, и отклоняет все остальные и возвращает некоторые группы для трех компонентов). Я попробовал и придумал следующее:

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

IMO (я не тестировал широко), это должно нормально работать в качестве валидатора для ввода, но проблема в том, что это регулярное выражение не предлагает способ получения компонентов. Для этого вам все равно нужно разделить период.

Это решение не является универсальным, но в большинстве случаев при программировании этого не требуется. Конечно, это зависит от других ограничений, которые могут быть в вашем коде.

rslite
источник
3

Указание элементов 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>
Эммерсон
источник
3

Я считаю это хорошим упражнением - vparse , у которого есть крошечный исходник , с простой функцией:

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;
}
Виталий-Т
источник
3

Для разбора номеров версий, которые следуют следующим правилам: - Только цифры и точки - Не могут начинаться или заканчиваться точкой - Не могут быть двумя точками вместе

Это помогло мне.

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

Допустимые случаи:

1, 0.1, 1.2.1

привязка
источник
2

Еще одно решение:

^[1-9][\d]*(.[1-9][\d]*)*(.\*)?|\*$
Александр Ярушевский
источник
2

Иногда номера версий могут содержать второстепенную буквенно-цифровую информацию (например, 1.2.0b или 1.2.0-beta ). В этом случае я использую это регулярное выражение:

([0-9]{1,4}(\.[0-9a-z]{1,6}){1,5})
Марк Рюф
источник
1

Я нашел это, и у меня это работает:

/(\^|\~?)(\d|x|\*)+\.(\d|x|\*)+\.(\d|x|\*)+
Или Ассаяг
источник