George V. Reilly

Checking minimum version numbers in Bash

Version Sacrifice

I worked on a Bash script today that sets up various pre­req­ui­sites for our build. We need a recent version of Docker but our Bamboo build agents are running on Ubuntu 14.04, which has a very old version of Docker. The script upgrades Docker when it’s first run. The script may be run more than once during the lifetime of the agent, so the second and subsequent calls should not upgrade Docker.

Basically, I wanted

if $DOCKER_VERSION < 1.9; then upgrade_docker; fi

Un­for­tu­nate­ly, it’s not that easy in Bash. Here’s what I came up with.

install_latest_docker() {
    if docker --version | python -c "min=[1, 9]; import sys; ↩
v=[int(x) for x in sys.stdin.read().split()[2].split(',')[0].split('.')]; ↩
sys.exit(v < min)";
    then
        echo "Docker up to date"
        return
    fi
    # Install Docker ...

Let’s unpack that ugly if one-liner. Note: the denotes linebreaks introduced for pre­sen­ta­tion purposes; it’s all one line.

$ docker --version
Docker version 1.9.1, build a34a1d5
$ ipython

In [1]: dv = 'Docker version 1.9.1, build a34a1d5'

In [2]: dv.split()
Out[2]: ['Docker', 'version', '1.9.1,', 'build', 'a34a1d5']

In [3]: dv.split()[2]
Out[3]: '1.9.1,'

In [4]: # Remove trailing comma

In [5]: dv.split()[2].split(',')
Out[5]: ['1.9.1', '']

In [6]: dv.split()[2].split(',')[0]
Out[6]: '1.9.1'

In [7]: # Break apart at dots

In [8]: dv.split()[2].split(',')[0].split('.')
Out[8]: ['1', '9', '1']

# String comparisons aren't good enough for version numbers
In [9]: ['1', '10'] < ['1', '9']
Out[9]: True

# We have to convert each token to an integer, then lexicographically compare
In [10]: [1, 10] < [1, 9]
Out[10]: False

# Convert to list of integers
In [11]: version = [int(x) for x in dv.split()[2].split(',')[0].split('.')]

In [12]: min = [1, 9]

In [13]: version, min
Out[13]: ([1, 9, 1], [1, 9])

In [14]: version < min
Out[14]: False

In [15]: [1, 4, 2] < min
Out[15]: True

In [16]: int(version < min)
Out[16]: 0

In [17]: int([1, 4, 2] < min)
Out[17]: 1

The ugly triple split inside the list com­pre­hen­sion produces a list of integers, which can be compared lex­i­co­graph­i­cal­ly against min, another list. sys.exit is called with 1 when Docker’s version is less than min.

When $? is non-zero in Bash, then if some_­com­mand fails and the else clause (none here) is executed.

I came across Python Oneliner, while debugging my script. I might use Oneliner in other cir­cum­stances.

blog comments powered by Disqus
Christmas Cake » « Running