0

I'm trying to write a script that detects the Linux OS and version and through the use of conditions and operators execute different commands depending on the result.

Detect OS

if [ -f /etc/os-release ]; then 
    . /etc/os-release 
    OS="$NAME" 
    OS_version="$VERSION_ID" 
elif [ -f /etc/debian_version ]; then 
    # Older Debian/Ubuntu/etc. 
    OS="Debian" 
    OS_version="$(cat /etc/debian_version)" 
fi

If I echo these variables they return with

$ echo $OS 
Debian GNU/Linux

$ echo $OS_version 
10

In my example below I want to match Debian and OS version 10. However, the $OS variable contains more that just the word Debian so I need some kind of wildcard so I can do a partial match.

The only way I've found to get this to work in through command substitution by echoing the $OS variable to stdout and piping it into the grep command which then does a wildcard search. This is the if statement I came up with in the end:

if [ "$(echo "$OS" | grep 'Debian*')" -a "$OS_version" -ge 10 ]; then 
    echo "OS is Debian" 
    sleep 4 
else  
    echo "OS is other" 
    sleep 4 
fi

As I will be using this script on other Unix based OSs so I would like to make this script as widely compatible as possible and stick to POSIX standards.

While testing my script on Ubuntu 21.04 I discovered that Linux has trouble working out which number is greater than another when there is decimal place involved. Below I've created two shell scripts which export a two-integer number with a two-decimal place and uses an if statement to check if it's greater and/or equal to another number.

20.10.sh

#!/bin/sh
export OS_version="20.10"

if [ "$OS_version" -ge 21.04 ]; then
    echo "Your OS is new enough"
    sleep 2
else
    echo "Your OS is too old!"
    sleep 2
fi

20.10.sh output

~$ ./20.10.sh 
YOUR OS VERSION IS >> 20.10 
./20.10.sh: 6: [: Illegal number: 20.10 
Your OS is too old!

23.45.sh

#!/bin/sh
export OS_version="23.45" 
if [ "$OS_version" -ge 21.04 ]; then 
    echo "Your OS is new enough" 
    sleep 2 
else 
    echo "Your OS is too old!" 
    sleep 2 
fi

23.45.sh output

~$ ./23.45.sh 
YOUR OS VERSION IS >> 23.45 
./23.45.sh: 6: [: Illegal number: 23.45 
Your OS is too old!

The 23.45.sh script should echo "Your OS is new enough" but doesn't and displays an error of an illegal number.

Is there a way to detect the size of different numbers with decimal places in?

2

3 Answers 3

2

The standard [ command only supports decimal integers for its numeric comparison operators. Some [ implementations such as the [ builtin of ksh93 do support floats as well as an extension, but that's not portable, and anyway float comparison is not the same as version comparison.

For instance 10.2 as a float is greater than 10.10, but it's the reverse when they are interpreted as versions.

Ubuntu releases are dates. 20.04 (April 2020) is before 20.10 (October 2010). For those, you can use lexical comparison (which you can do with awk), provided you don't go as far back as 9.10 (Karmic Koala) or earlier and the minor number (as in 18.04.4) is not included (or if it does that it never goes up to 10).

1.2pre5-beta1.2 is less than 1.2 while 1.2a is generally considered greater than 1.2. It's generally difficult to compare versions without knowing the version naming convention.

On GNU systems, you may want to defer to GNU sort's --version-sort algorithm.

version_greater_than() {
  local before_sort="$1"$'\n'"$2"
  [ "$before_sort" != "$(printf %s "$before_sort" | sort --stable --version-sort)" ]
}

(in ksh93/zsh/bash syntax) Or its sh syntax equivalent:

version_greater_than() {
  [ "$1
$2" != "$(printf '%s\n' "$1" "$2" | sort --stable --version-sort)" ]
}

And then, you can do:

if version_greater_than 10.10 10.4; then
  echo 10.10 greater than 10.4
fi

POSIXly, you could take an awk approach where you zero-pad all sequences of numbers to an arbitrary length and compare lexically in the C locale:

compare_versions() {
  LC_ALL=C awk -- '
    function pad(v,  ret) {
      while (match(v, /[0-9]+/)) {
        ret = ret substr(v, 1, RSTART - 1) \
              sprintf("%09d", substr(v, RSTART, RLENGTH))
        v = substr(v, RSTART + RLENGTH)
      }
      return ret v
    }
    BEGIN {exit !(pad(ARGV[1]) '"$2"' pad(ARGV[2]))}' "$1" "$3"
}

To use as:

if compare_versions 1.10 '>' 1.2; then
  echo '1.10 > 1.2'
fi
0

You cannot use operators such as -lt or -ge for fixed- or floating-point comparisons; they are strictly integer math operators. bash (for instance) doesn't know how to do non-integer maths. You will have to compare the version numbers as strings using e. g. < or >=, while also being aware that e. g. 1.1 is "greater than" 1.10. You can work around this by building sort -v into your logic.

1
  • The question here is about the [ command, not bash. Most shells have [ builtin, but again, the OP is using sh not bash. The [ builtin of some sh implementations support floats in operands of their -lt/-gt operator, but that's not standard and not the case with the dash shell as typically used for sh in the systems refered to by the OP. Feb 24, 2022 at 18:48
0

As long as the version numbers are floats (and not something like 20.04.3), you can just pipe the comparison into bc.

For example, change this:

if [ "$OS_version" -ge 21.04 ]; then

To this:

if [ $(echo "$OS_version >= 21.04" | bc) ]; then

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .