8

I'm looking for the best bash one-liner to check if the version number is >= to a specific number. For example I have:

$ bash --version
GNU bash, version 4.3.48(1)-release (x86_64-pc-linux-gnu)
Copyright (C) 2013 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>

This is free software; you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.

In order to use associative arrays the bash version number must be >=4. In my bash script I'd like to put in a one-liner test in the most elegant / efficient / readable way possible.

I anticipate many answers to accomplish the goal so all that work will be gratefully up-voted.

Although this question is specific to bash version number, with modification the answers could apply to other applications such as LibreOffice, Python, systemd, kernel, etc. which might provide different functionality based on version number.


I'm adding an additional example because there can be many programs you may call in your application and some may need to be at a specific version.

Here is a real life example:

$ yad --version
0.37.0 (GTK+ 3.18.9)

YAD (Yet Another Dialog box) released version 0.39 on April 27, 2017 and it hasn't made it's way to Ubuntu repositories yet. To use new features I would have to manually download and compile from source. If I do this the test would be >= 0.39.

Further --version examples from testing (first line displayed only):

$ grep --version
grep (GNU grep) 2.25

$ /bin/dd --version
dd (coreutils) 8.25

$ /bin/rm --version
rm (GNU coreutils) 8.25

$ ls --version
ls (GNU coreutils) 8.25

Great answers have been posted for BASH version checking ONLY. I'm hoping for a robust script that works for more than bash. Perhaps "--version" can be universally appended after the program name in the script? Does the version number compare properly? ie. A simple string test "4.9.10 > 4.11.1" probably returns true in bash but it should be false.


Comparing version numbers with decimal separators

There are many ways of comparing two version numbers containing decimal separators as answered in stack overflow. The question and accepted answer were posted in 2010 but, I'm leaning towards the answer posted December 2016.

closed as too broad by user364819, Eric Carvalho, Ravexina, user117103, TheWanderer May 27 '17 at 22:01

Please edit the question to limit it to a specific problem with enough detail to identify an adequate answer. Avoid asking multiple distinct questions at once. See the How to Ask page for help clarifying this question. If this question can be reworded to fit the rules in the help center, please edit the question.

  • Have you looked at the $BASH_VERSION and $BASH_VERSINFO variables? – steeldriver May 20 '17 at 0:12
  • @steeldriver No. That would be an acceptable answer in bash specific case if you'd like to post it. However for other cases the environmental variables may not be available. – WinEunuuchs2Unix May 20 '17 at 0:15
  • By the way, related: askubuntu.com/questions/39309/… – Sergiy Kolodyazhnyy May 20 '17 at 3:42
  • @SergiyKolodyazhnyy that is related to bash only. Although I'm looking for a bash one-liner or script / function checking the bash version number only is not the ultimate goal. A generic routine to check all programs by appending --version and testing the output was the original intention. I've edited the question accordingly. – WinEunuuchs2Unix May 20 '17 at 16:25
  • Version number formats vary wildly across programs. The way they report version details also vary wildly. It's simply not practical to ask for a way to check a random program's version. This question is too broad. – muru May 21 '17 at 11:43
11

Try:

$ [ "${BASH_VERSINFO:-0}" -ge 4 ] && echo "bash supports associative arrays"
bash supports associative arrays

BASH_VERSINFO is a readonly array variable whose members hold version information for this instance of bash. Since it was introduced with bash 2.0, it is likely supported by all bash versions you will encounter. But, to be cautious, we include a default value of of 0 for any earlier version bash for which this variable is unset.

Extracting version information from other programs

You asked about LibreOffice, Python, kernel, etc.

LibreOffice produces version information that looks like:

$ libreoffice --version
LibreOffice 5.2.6.2 20m0(Build:2)

To extract the version number:

$ libreoffice --version | cut -d' ' -f2
5.2.6.2

For python:

$ python -c 'import platform; print(platform.python_version())'
2.7.13

To obtain the kernel version, use uname:

$ uname -r
4.9.0-2-amd64
  • Great answers! Just a note that your uname -r is "4.9.0-2-amd64" which could test greater than mine "4.11.1-041101-generic" with a regular bash test when in reality my version number is greater. – WinEunuuchs2Unix May 20 '17 at 1:32
  • 2
    @WinEunuuchs2Unix Python tools can accurately compare version strings. See Compare version strings in Python – wjandrea May 20 '17 at 2:08
  • John-- the python example can be shortened with $ python --version which returns Python 2.7.12. @wjandrea-- thanks for the link +1. Perhaps I could build a table of all called program names and minimal version numbers. Then pass the table to a modified copy of the python link you provided. Because only compiled Python can be called by grub you would think a binary exists to do this or its possible in shell. – WinEunuuchs2Unix May 20 '17 at 16:21
6

Instead of comparing version numbers, you could test directly for the feature itself. declare -A returns 2 (at least in Bash 3.2) if it doesn't recognize -A, so test for that (it also prints an error):

unset assoc
if ! declare -A assoc ; then
    echo "associative arrays not supported!"
    exit 1
fi

(declare -A var also fails if var is a non-associative array, so unset it first.)

While I don't really assume anyone's going to backport features in Bash, in general it's more appropriate to check for the features, not the versions. Even in Bash's case, someone might compile a version with only limited features...


The more general case of testing version numbers has two parts: 1) how to find the correct version number to test, and 2) how to compare it against another value.

The first is the more difficult one. Many programs tell their version number with a command line flag like --version or -v, but the output format varies and programmatically picking the version number may be difficult. Then there's the issue of possibly having several versions of the same program installed at the same time.

The second depends on some knowledge of the format of the version numbers. dpkg can compare Debian-style version numbers (which I think includes semver type versions as subset):

if dpkg --compare-versions 4.3.30 ge 4.0.0 ; then
    echo "it's version 4.x"
fi

Or, just to combine the above:

bashver=$( bash --version | sed -Ee 's/GNU bash, version ([0-9.]+).*/\1/;q' )
if dpkg --compare-versions "$bashver" ge 4.0.0 ; then
    echo "'bash' in your path is version 4.x"
fi
4

There's couple ways to approach what you want to achieve.

1. Use $BASH_VERSION

It's sufficient to just to see what's in $BASH_VERSION variable. Personally I'd use subshell like so:

$ (read -d "." version trash <<< $BASH_VERSION; echo "$version" )
4

Note that <<< syntax for here-doc is not portable, if you're going to use it with /bin/sh, which is Dash on Ubuntu and might be something else on a different system

Alternative way is via case statement or if statement. Personally, I'd do this:

bash-4.3$ case $BASH_VERSION in 4.*) echo "Can use associative arrays";; ?) echo "can't use associative arrays" ;; esac
Can use associative arrays

Probably for the sake of portability , you probably should check if such variable is even set at all in the first place with something like [ -n $BASH_VERSION ]

This totally can be rewritten as function to be used in a script. Something a long the lines of:

#!/bin/bash
version_above_4(){
    # check if $BASH_VERSION is set at all
    [ -z $BASH_VERSION ] && return 1

    # If it's set, check the version
    case $BASH_VERSION in 
        4.*) return 0 ;;
        ?) return 1;; 
    esac
}

if version_above_4
then
    echo "Good"
else
    echo "No good"
fi

This is not a one-liner, although this is much better. Quality over quantity.

2. Check what's installed

For that you need to filter output of apt-cache policy like so

$ apt-cache policy bash | awk -F '[:.]' '/Installed:/{printf "%s\n",substr($2,2)}'
4

dpkg-query can also come in handy with some filtering via awk.

$ dpkg-query -W bash | awk '{print substr($2,1,1)}'   
4

Note that this is not portable, since if there's no dpkg or apt installed on a system ( for instance, RHEL or FreeBSD ), it won't do you any good.

3. Use set -e to exit script if there's an error

One way to get around it is just simply go ahead an use associative arrays and quit when bash cannot use them. set -e line below #!/bin/bash will allow the script to quit if the script can't use associative array.

This will require you to explicitly tell the user: "Hey, you really need bash version 4.3 or above, otherwise the script won't work". Then the responsibility rests with the user, although some might argue that this is not really a good approach to software development.

4. Abandon all hope and write portable, POSIX-compliant scripts

bash scripts aren't portable because its syntax isn't compatible with Bourne shell. If the script that you're writing is going to be used on a range of different systems, not just Ubuntu alone, then abandon all hope, and find ways to use something other than associative arrays. That might include having two arrays or parsing a configuration file. Consider also switching to a different language, Perl or Python, where syntax is at least more portable than bash.

  • The associative array is the second of "two arrays" as you suggest in section 4. It's sole purpose is to hold the key "path/to/file-name" and the value is the index within the main array. The associative array was created to speed up searches into the regular indexed array which are currently sequential. Although I have my eye on Python anyway, my current bash compatibility is focused on Ubuntu and perhaps Bash for Windows 10. I like your script which can be tailored. For example yad --version returns 0.37.0 (GTK+ 3.18.9) but new features are currently in 0.39. – WinEunuuchs2Unix May 20 '17 at 5:16
1

One-liner not possible but a bash script is possible

I developed a script that draws on answers in Stack Overflow. One of those answers led to a Dell Employee writing version number comparisons in 2004 for the DKMS application.

The initial version number comparison code I mentioned in my question did not work (for me at least).

Sample outputs:

───────────────────────────────────────────────────────────────────────────────
rick@dell:~$ testver perl 5.22.1
11= greater, 10= same and 9= less: 10
───────────────────────────────────────────────────────────────────────────────
rick@dell:~$ testver perl 5.22.3
11= greater, 10= same and 9= less: 9
───────────────────────────────────────────────────────────────────────────────
rick@dell:~$ testver perl 5.22.0
11= greater, 10= same and 9= less: 11
───────────────────────────────────────────────────────────────────────────────
rick@dell:~$ testver crazy 5.22.0
Command: crazy not found. Check spelling.
───────────────────────────────────────────────────────────────────────────────
rick@dell:~$ testver perl 5..22.0
Version number: 5..22.0 has invalid format. Aborting.
───────────────────────────────────────────────────────────────────────────────
rick@dell:~$ testver yad 0.39
11= greater, 10= same and 9= less: 9
───────────────────────────────────────────────────────────────────────────────
rick@dell:~$ testver yad 0.37
11= greater, 10= same and 9= less: 10
───────────────────────────────────────────────────────────────────────────────
rick@dell:~$ 

The code

The bash script below needs to be marked as executable using the command chmod a+x script-name. I'm using the name /usr/local/bin/testver:

#!/bin/bash

# NAME: testver
# PATH: /usr/local/bin
# DESC: Test a program's version number >= to passed version number
# DATE: May 21, 2017.

# CALL: testver Program Version

# PARM: 1. Program - validated to be a command
#       2. Version - validated to be numeric

# NOTE: Extracting version number perl one-liner found here:
#       http://stackoverflow.com/questions/16817646/extract-version-number-from-a-string

#       Comparing two version numbers written by Dell employee for DKMS application:
#       http://lists.us.dell.com/pipermail/dkms-devel/2004-July/000142.html

# Map parameters to coder-friendly names.
Program="$1"
Version="$2"

# Program name must be a valid command.
command -v $Program >/dev/null 2>&1 || { echo "Command: $Program not found. Check spelling."; exit 99; }

# Passed version number must be valid format.
if ! [[ $Version =~ ^([0-9]+\.?)+$ ]]; then
    echo "Version number: $Version has invalid format. Aborting.";
    exit 99
fi

# Get current version number of installed program
InstalledVersion=$( "$Program" --version | perl -pe '($_)=/([0-9]+([.][0-9]+)+)/' )

# Sanity check
if ! [[ $InstalledVersion =~ ^([0-9]+\.?)+$ ]]; then
    echo "Invalid version number: $InstalledVersion found for command: $Program"
    exit 99
fi

version_checker() {
    local ver1=$InstalledVersion
    while [ `echo $ver1 | egrep -c [^0123456789.]` -gt 0 ]; do
        char=`echo $ver1 | sed 's/.*\([^0123456789.]\).*/\1/'`
        char_dec=`echo -n "$char" | od -b | head -1 | awk {'print $2'}`
        ver1=`echo $ver1 | sed "s/$char/.$char_dec/g"`
    done    
    local ver2=$Version
    while [ `echo $ver2 | egrep -c [^0123456789.]` -gt 0 ]; do
        char=`echo $ver2 | sed 's/.*\([^0123456789.]\).*/\1/'`
        char_dec=`echo -n "$char" | od -b | head -1 | awk {'print $2'}`
        ver2=`echo $ver2 | sed "s/$char/.$char_dec/g"`
    done    

    ver1=`echo $ver1 | sed 's/\.\./.0/g'`
    ver2=`echo $ver2 | sed 's/\.\./.0/g'`

    do_version_check "$ver1" "$ver2"
}

do_version_check() {

    [ "$1" == "$2" ] && return 10

    ver1front=`echo $1 | cut -d "." -f -1`
    ver1back=`echo $1 | cut -d "." -f 2-`
    ver2front=`echo $2 | cut -d "." -f -1`
    ver2back=`echo $2 | cut -d "." -f 2-`

    if [ "$ver1front" != "$1" ] || [ "$ver2front" != "$2" ]; then
        [ "$ver1front" -gt "$ver2front" ] && return 11
        [ "$ver1front" -lt "$ver2front" ] && return 9

        [ "$ver1front" == "$1" ] || [ -z "$ver1back" ] && ver1back=0
        [ "$ver2front" == "$2" ] || [ -z "$ver2back" ] && ver2back=0
        do_version_check "$ver1back" "$ver2back"
        return $?
    else
        [ "$1" -gt "$2" ] && return 11 || return 9
    fi
}

version_checker "$InstalledVersion" "$Version"
TestResults=$?
echo "11= greater, 10= same and 9= less: $TestResults"

[[ $TestResults -eq 9 ]] && exit 1 ;
exit 0

This code isn't as clean as I'd like and reflects the fact parts are written by three different people.

Not the answer you're looking for? Browse other questions tagged or ask your own question.