A regex for version number parsing

I have a version number of the following form:

version.release.modification

where version, release and modification are either a set of digits or the '*' wildcard character. Additionally, any of these numbers (and any preceding .) may be missing.

So the following are valid and parse as:

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

But these are not valid:

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

Can anyone provide me a not-too-complex regex to validate and retrieve the release, version and modification numbers?


Solution 1:

I'd express the format as:

"1-3 dot-separated components, each numeric except that the last one may be *"

As a regexp, that's:

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

[Edit to add: this solution is a concise way to validate, but it has been pointed out that extracting the values requires extra work. It's a matter of taste whether to deal with this by complicating the regexp, or by processing the matched groups.

In my solution, the groups capture the "." characters. This can be dealt with using non-capturing groups as in ajborley's answer.

Also, the rightmost group will capture the last component, even if there are fewer than three components, and so for example a two-component input results in the first and last groups capturing and the middle one undefined. I think this can be dealt with by non-greedy groups where supported.

Perl code to deal with both issues after the regexp could be something like this:

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

Which isn't really any shorter than splitting on "." ]

Solution 2:

Use regex and now you have two problems. I would split the thing on dots ("."), then make sure that each part is either a wildcard or set of digits (regex is perfect now). If the thing is valid, you just return correct chunk of the split.

Solution 3:

This might work:

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

At the top level, "*" is a special case of a valid version number. Otherwise, it starts with a number. Then there are zero, one, or two ".nn" sequences, followed by an optional ".*". This regex would accept 1.2.3.* which may or may not be permitted in your application.

The code for retrieving the matched sequences, especially the (\.\d+){0,2} part, will depend on your particular regex library.

Solution 4:

Thanks for all the responses! This is ace :)

Based on OneByOne's answer (which looked the simplest to me), I added some non-capturing groups (the '(?:' parts - thanks to VonC for introducing me to non-capturing groups!), so the groups that do capture only contain the digits or * character.

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

Many thanks everyone!

Solution 5:

My 2 cents: I had this scenario: I had to parse version numbers out of a string literal. (I know this is very different from the original question, but googling to find a regex for parsing version number showed this thread at the top, so adding this answer here)

So the string literal would be something like: "Service version 1.2.35.564 is running!"

I had to parse the 1.2.35.564 out of this literal. Taking a cue from @ajborley, my regex is as follows:

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

A small C# snippet to test this looks like below:

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        
}