Search code examples
bashperlcomparisonversion

Cannot compare two version string by calling perl oneliner from bash


I want a bash function to return 0 if the result of one comparison between to version numbers is true and to return 1 if it's false so here's what I do :

$ perl -e "exit(!(0.9.8.8 < 0.9.10.0))"
$ echo $?
1

But it does not work because if I change the comparison sign, the exit code is the same :

$ perl -e "exit(!(0.9.8.8 > 0.9.10.0))"
$ echo $?
1

This simple code works with floats but not with version numbers, how can I make my code to work for version numbers ?


Solution

  • Perl literals like 0.1.2.3 are interpreted as vstrings and have magic associated with them:

    A literal of the form v1.20.300.4000 is parsed as a string composed of characters with the specified ordinals. This form, known as v-strings, provides an alternative, more readable way to construct strings, rather than use the somewhat less readable interpolation form "\x{1}\x{14}\x{12c}\x{fa0}". This is useful for representing Unicode strings, and for comparing version "numbers" using the string comparison operators, cmp, gt, lt etc. If there are two or more dots in the literal, the leading v may be omitted.

    So when you compare 0.9.8.8 < 0.9.10.0 you are comparing to vstrings numerical less than, this would lead to a warning like (if you enable warnings):

    Argument "\0^I\n\0" isn't numeric in numeric lt (<) at -e line 1.
    

    You should use string comparison for vstrings, see this blog post for more information.

    However, it is better to use the version module to compare versions.

    According to the documentation:

    If you need to compare version numbers, but can't be sure whether they are expressed as numbers, strings, v-strings or version objects, then you should use version.pm to parse them all into objects for comparison.
    [...]
    Version objects overload the cmp and <=> operators. Perl automatically generates all of the other comparison operators based on those two so all the normal logical comparisons will work. This may give surprising results:

    $v1 = version->parse("v0.97.0");
    $bool = $v1 > 0.96; # FALSE since 0.96 is v0.960.0
    

    Always comparing to a version object will help avoid surprises.

    So you can do:

    perl -Mversion -e 'exit !(version->parse("0.9.8.8")<version->parse("0.9.10.0"))'