9

I am attempting to write a script depending on the version of Debian.

I have tried the following:

#! /bin/bash DEBVERS=$(awk '{print $1}' /etc/debian_version) echo "DEBVERS = " $DEBVERS if [[ $DEBVERS > 12 ]]; then echo "Trixie" fi if [[ $DEBVERS < 13 ]]; then echo "not Trixie" fi if [[ $DEBVERS -gt 12 ]]; then echo "Trixie" fi # if (( $DEBVERS > 12 )); then # echo "Trixie" # fi # if (( $DEBVERS -gt 12 )); then # echo "Trixie" # fi 

I thought I had the 2nd working (on Bookworm):

DEBVERS = 12.11 Trixie not Trixie 

But when it tried with earlier versions it failed to output:

DEBVERS = 9.13 Trixie 

Can anyone suggest a reliable method? (I have tried other methods without success.)

11
  • 4
    DEBVERS contains a floating point value. The Unix & Linux answer Comparing floats with an if-else statement in a shell script says bash only supports integer numbers for comparison, and has some examples of using bc for floating point comparisons. This is a comment, and not an answer, since haven't attempted to create my own example. Commented Aug 3 at 12:50
  • 3
    Do the answers to How to compare a program's version in a shell script? help? Commented Aug 3 at 13:03
  • 6
    @ChesterGillon Version numbers are not floating point values, because version 1.10 > 1.9 which is not true for real numbers. They are grouped integers. Commented Aug 4 at 9:22
  • 1
    You can split off the major integer part of a (multi-)group value using a construct like this v=12.34.56; echo "${v%%.*}"12. Was this your sticking point? Commented Aug 4 at 11:32
  • Bash only does integer arithmetic. There are easier ways than the answers suggest. In bash, a number variable is a string variable and there is an easy way to locate the decimal point and strip off everything after it. This is left as an exercise for the user. Commented Aug 4 at 16:15

10 Answers 10

14

No need for Python, awk or bc etc. Bash can strip off the minor part of the version number:

$ cat t.sh #! /bin/bash DEBVERS=12.11 echo "DEBVERS = " $DEBVERS major=${DEBVERS%.*} echo "major = " $major if [[ $major -ge 13 ]]; then echo "Trixie or newer" else echo "older than Trixie" fi 

result

$ ./t.sh DEBVERS = 12.11 major = 12 older than Trixie 

Note that using > for the version number comparison here is wrong, since it does a string comparison, where e.g. 2 is "greater than" 12. Use -lt/-le/-gt/-ge for numeric comparisons.

(Also, saying that anything with major release higher than 12 is Trixie is wrong, since Trixie is Debian 13 in particular.)

The above should work with any version of Bash from at least Bash 3.2.


Addendum I

A note about parameter expansion, command substitution etc

The following doesn't work:

MPG=30; echo $MPGMPG 

This does:

MPG=30; echo ${MPG}MPG 

which is a handy notation to know

Shells such as Bash extend the utility of curly braces further. Run man bash and then search for /^ *Parameter Expansion and page down a few times to

 ${parameter%word} ${parameter%%word} Remove matching suffix pattern. The word is expanded to produce a pattern just as in pathname expansion, and matched against the expanded value of parameter using the rules described under Pattern Matching be‐ low. If the pattern matches a trailing portion of the expanded value of parameter, then the result of the expansion is the expanded value of parameter with the shortest matching pattern (the ``%'' case) or the longest matching pattern (the ``%%'' case) deleted. 

For example I often use this feature to extract foo from ./logs/foo.log when constructing temporary filenames etc.

Under "Pattern Matching" note that . is a literal character and not a pattern for any character as it is in grep etc. * matches any string and doesn't mean what it means in grep. So .* matches a literal stop followed by any string. The % causes this to be removed.

Addendum II

A note about version numbers

It is often a mistake to think of version numbers as numbers even though some version numbers look like numbers and their name suggests they are numbers.

For example, it is not useful to divide the version number of bash by the version number of Debian. It is not useful to subtract the version number of X11 from the version number of awk. We have no use for the square root of a version number.

I wouldn't try arithmetic methods such as int() or comparators on version numbers - if only because version 1.2.3 will cause heartache.

5
  • Works well; I am not sure how ${DEBVERS%.*} works Commented Aug 4 at 3:10
  • @Millways: I added an addendum to the answer which I hope helps. Commented Aug 4 at 9:56
  • Thanks this made it clear. Another tool to add to my arsenal. Commented Aug 4 at 22:35
  • Yes, the OP obviously wanted to compare the major number only, and this is what the answer does. However I wonder why you add an extra space in echo "major = " $major: Why not simply say echo "major=$major" (avoiding all the spaces), or maybe even better echo "major=\"$major\""? Commented Aug 5 at 12:11
  • @U.Windl I originally just copied OPs script and made minimal changes. There have been a few revisions since. In retrospect it may have been cleaner to write a new script but originally I just wanted to highlight the critical part. Commented Aug 5 at 12:16
4

Assuming things to be equal to Ubuntu.

source /etc/os-version;echo $PRETTY_NAME 

Will print a proper name and version

cat /etc/os-version 

... for more options.

Actually comparing numbers:

  • Bash itself does not use float numbers, i.e. numbers that includes decimals.
  • python -c "..." can be used for numeric calculations
  • more in Bash guides at wwww.tldp.org

Yet another way:

$ python -c "import sys;exit( ( int(sys.argv[1]) == 12) )" 12; echo $? 1 $ python -c "import sys;exit( ( int(sys.argv[1]) == 12) )" 11; echo $? 0 

Result depending on $DEBVERS

$ python -c "exit( ( int("$DEBVERS") == 12) )"; echo $? # value printed depends on $DEBVERS content 
5
  • The file should contain numbers also. If with decimals, then use int-function in bc Commented Aug 3 at 8:46
  • How to use e.g. python... Just realized bc doesn't have int bugger Commented Aug 3 at 8:56
  • stackoverflow.com/a/61469003/3720510 Commented Aug 3 at 9:01
  • " I want to run different code depending on version. " i assume you did find the version numbers in /etc/-os-version in the end. Commented Aug 11 at 19:34
  • There are more than one way to do things... Educate yourself. Commented Aug 12 at 11:07
4

Rather than a list of if … else if … else … use a case statement. Here we also split off the major version number from a potentially non-integer version number.

#!/bin/sh [ -f /etc/os-release ] && . /etc/os-release MAJOR_VERSION_ID="${VERSION_ID%%.*}" case "${ID:-?}-${MAJOR_VERSION_ID:-?}" in debian-13) echo "Trixie" ;; debian-12) echo "Bookworm" ;; debian-*) echo "Some other Debian release (${MAJOR_VERSION_ID:-?})" ;; *) echo "Something else entirely (${ID:-?}-${MAJOR_VERSION_ID:-?})" ;; esac 

Although in the trivial sense,

[ -f /etc/os-release ] && . /etc/os-release MAJOR_VERSION_ID="${VERSION_ID%%.*}" echo "Version ${MAJOR_VERSION_ID:-?} (${VERSION_ID:-?}) has code name ${VERSION_CODENAME:-?} and distribution ${ID:-?}" 
4

The script below removes any . characters and adds 0 characters to the strings before comparing. Some examples are given in the table below.

Parameter 1
Passed to cmpver
Parameter 3
Passed to cmpver
Converted
Parameter 1
Converted
Paramenter 3
9.13 12 0913 1200
13.0 12 1300 1200
12.11 13 1211 1300
123.1 123.10 123001 123010
2.12 2.7 0212 0207
2.12.1 2.12 021201 021200

This conversion allows the < and > operators to correctly compare the strings.

#! /bin/bash DEBVERS=$(awk '{print $1}' /etc/debian_version) echo "DEBVERS = " $DEBVERS pad() { eval 'local -a A=("${A'$1'[@]}")' while [[ ${#A[@]} -lt N ]]; do A+=(0); done printf "%0${M}s" "${A[@]}"; echo } parse() { local -i I IFS="." read -a $1 <<< "$2"; eval 'set "${'$1'[@]}"' for I; do if [[ ${#I} -gt M ]]; then M=${#I}; fi; done if [[ $# -gt N ]]; then N=$#; fi } cmpver() { local -i M=0 N=0 local -a A1 A3 parse A1 $1 parse A3 $3 [ "$(pad 1)" $2 "$(pad 3)" ] } if cmpver $DEBVERS ">" 12; then echo "Trixie" fi if cmpver $DEBVERS "<" 13; then echo "not Trixie" fi 

An alternate version of the function cmpver is given here. This alternate version does not require any additional user defined functions and allows for the <= and >= conditional operators.

0
4

My test script was an attempt to create a Minimal, Reproducible Example to compare numeric strings and execute different code depending on version (and other factors including CPU although that seemed an unnecessary complication).

It appears what I was attempting is not possible so I converted to integers and use the bash number comparison from How to Evaluate Strings as Numbers in Bash

I now have a working script:-

#! /bin/bash DEBVERS=$(awk '{print $1}' /etc/debian_version) echo "DEBVERS = " $DEBVERS DEBMAJVERS=${DEBVERS%.*} echo "DEBMAJVERS = " $DEBMAJVERS if (( DEBMAJVERS < 13 )); then echo "not Trixie" fi if (( DEBMAJVERS > 12 )); then echo "Trixie or later" fi if (( DEBMAJVERS < 11 )); then echo "pre Buster" fi 

Some outputs

DEBVERS = 9.13 DEBMAJVERS = 9 not Trixie pre Buster 
DEBVERS = 13.0 DEBMAJVERS = 13 Trixie or later 
DEBVERS = 12.11 DEBMAJVERS = 12 not Trixie 

Additional comments suggested other options but TIMTOWTDI.

3
  • Thanks to all who responded. There were a couple of helpful answers (although repeated use of awk works it would be a maintenance nightmare). Commented Aug 6 at 11:00
  • Wouldn't pre Buster mean before Buster? I ask because Buster is version 10 and you have DEBMAJVERS < 11. Commented Aug 6 at 21:33
  • I cannot find anywhere in How to Evaluate Strings as Numbers in Bash where there is an explanation of how to do bash number comparison. Commented Aug 7 at 4:05
3

You can try something like in pure awk:

awk '{if( $1+0 > 12) print "Trixie"; else print "not Trixie" }' /etc/debian_version 

The construction +0 is added to trick string to number conversion :)

Here is example to exec some code "after" the compare:

eval $(awk '{if( $1+0 > 12) print "ls"; else print "cat /etc/hosts" }' /etc/debian_version) 

in above example the program will exec ls is the version is >12 and cat /etc/hosts otherwise

5
  • I will try this but my real objective is to execute different code depending on version, not just print. I started off with awk '{if ($1 >= 13.0){ print "Debian DEBVERSion is 13 or greater";}}' /etc/debian_version awk '{if ($1 < 13.0){ print "Debian DEBVERSion is less than 13";}}' /etc/debian_version but am struggling to get this to execute different code. The internet seems to have lots of different constructs (some of which don't work). Commented Aug 3 at 12:55
  • @Milliways, what code do you want to exec? And the about your code above: there is no need to run twice awk Commented Aug 3 at 13:19
  • @Milliways, please check my edited answer Commented Aug 3 at 13:25
  • I have a suite of test programs in c & python using different libraries; some run on any version of OS others only run on specific OS versions. My question was a simplified example to try and focus on the detection of version. Commented Aug 3 at 13:38
  • @Milliways, I see. In second example I run two commands depend of version. So you can modify it for your purpose. Commented Aug 3 at 13:55
2

Using zsh:

#!/bin/zsh autoload -U is-at-least version=$1 if is-at-least 13 $version; then echo "trixie or newer" else echo "older" fi 
❯ ./test.sh 13 trixie or newer ❯ ./test.sh 12 older 

This also works no matter the weirdness of your version number:

❯ ./test.sh 12.45-abc older ❯ ./test.sh 13.45-abc trixie or newer 
2

You aren't comparing numeric strings, you're comparing version numbers. “Version numbers” are not actually numbers. Details can vary, but it is pretty much universal that 1.9 comes before 1.10, which is not the same as 1.1. So interpreting a version number as a decimal number is wrong.

In general, to compare version numbers, I recommend not trying to reinvent the wheel. There are commonplace tools that know how to compare version numbers. Note that these tools don't all implement exactly the same rules. The format V1.V2.V3… where V1, V2, etc. are digits is a common basis, but going beyond that, projects may have different rules. (For example, 1.2beta3 should be before 1.2 whereas 1.2patch3 should be after, but the exact strings that should be treated as “beta” vs “patch” are highly variable.)

GNU sort and FreeBSD sort (and maybe a few others) can sort lines with sort -V or sort --version-sort.

# True if version $1 is older than version $2 version_less () { printf '%s\n%s\n' "$1" "$2" | sort --version-sort --check } 

If you're working on Debian specifically, you can alternatively use dpkg --compare-versions. This implements the specific rules that are used for Debian packages.

For comparing Debian release versions, specifically, you should actually not use any of this. The minor number just indicates to what exact version the base-files package has been upgraded. This is not necessarily correlated with how up-to-date other packages are, and is often overridden in derived distributions. So you should really check only the major version.

Note that even the major version only works on Debian specifically and some but not all derivatives. For example, on Ubuntu, /etc/debian_version contains the name of the Debian testing version that the Ubuntu release is based on, but not its number. For most purposes, you'd be better off checking the versions of specific packages.

1

Since the OP already posted an answer, this answer will address both the OP's question and answer.

TL;DR

The OP used (( )) which requires passing a string to the let command. The let command must then parse by this string. Using [[ ]] can be parsed directly by Bash which should be more efficient.

A more detailed answer is given below.

Problem with the OP's Question

If DEBVERS contains a ., then the following script posted in the question would have produced a syntax error message when executed.

if [[ $DEBVERS -gt 12 ]]; then echo "Trixie" fi 

I assume the OP meant to comment out these lines. In other words, I assume the OP meant to post what is shown below instead.

#if [[ $DEBVERS -gt 12 ]]; then # echo "Trixie" #fi 

Ways to get the Version Number

The answer used the following.

DEBVERS=$(awk '{print $1}' /etc/debian_version) echo "DEBVERS = " $DEBVERS DEBMAJVERS=${DEBVERS%.*} echo "DEBMAJVERS = " $DEBMAJVERS 

If only DEBMAJVERS is needed, then what is shown below could be used instead.

read -d . DEBMAJVERS < /etc/debian_version echo "DEBMAJVERS = " $DEBMAJVERS``` 

Note that if you only needed the current version as a single integer, then what is shown below could also have been used.

DEBMAJVERS=$(lsb_release -s -r) echo "DEBMAJVERS = " $DEBMAJVERS``` 

How to Compare Integers

In both the question and answer, the OP uses if in the following form. Here command2 is only executes if command1 returns an exit status of 0.

if command1; then command2 fi 

The (( )) Method

The OP used the following. Here, command1 is (( DEBMAJVERS < 13 )).

if (( DEBMAJVERS < 13 )); then echo "not Trixie" fi 

What is shown above is the same as what is shown below. In other words, Bash treats both the exact same way.

if let "DEBMAJVERS < 13"; then echo "not Trixie" fi 

The string passed to let is evaluated as if written in the C language. If the variable DEBMAJVERS contains an integer, then the string will evaluate to a value of 1, when this integer is less than 13, otherwise the string will evaluate to a value of 0. The let command returns an exit value of 0 when the result of the evaluation is nonzero, otherwise a value of 1 is returned.

The [[ ]] Method

The OP used the following. Here, command1 is [[ $DEBVERS -gt 12 ]]. As I already stated, executing this command would produce a syntax error message, when DEBVERS contains a . character.

if [[ $DEBVERS -gt 12 ]]; then echo "Trixie" fi 

The script below would eliminate the error message. Note that since this is an integer comparison DEBMAJVERS does not have to be preceded by a $ character.

if [[ DEBMAJVERS -gt 12 ]]; then echo "Trixie" fi 

The follow arithmetic binary operators can be used with the [[ conditional command.

Operator Decription
-eq equal to
-ne not equal to
-lt less than
-le less than or equal to
-gt greater than
-ge greater than or equal to

The [ ] Method

Note
I have included this method even though the OP did not post trying this method.
if [ $DEBMAJVERS -lt 13 ]; then echo "not Trixie" fi 

What is shown above is the same as what is shown below. In other words, Bash treats both the exact same way.

if test $DEBMAJVERS -lt 13; then echo "not Trixie" fi 

If the variable DEBMAJVERS contains an integer, then the test command returns an exit value of 0, when this integer is less than 13, otherwise an exit value of 1 is returned.

The Strings Method

The OP tried the following, which failed to work as desired when DEBVERS contained 9.13.

if [[ $DEBVERS > 12 ]]; then echo "Trixie" fi 

If you wish to string comparisons to work properly, then 0 characters may need to be prepended to the strings before comparing. What is shown below would work provided the version number never exceeds 99999. Note that first . character and all characters to the right of the first . character are removed before prepending any 0 characters.

printf -v DEBMAJVERS "%05s" ${DEBVERS%%.*} echo "DEBMAJVERS = " $DEBMAJVERS if [[ $DEBMAJVERS > 00012 ]]; then echo "Trixie" fi 

Issue with OP's Answer.

The OP references the article How to Evaluate Strings as Numbers in Bash. This article uses an example which is repeated below. The example uses the arithmetic expansion (i.e. $((X))) instead of simply first adding the integer attribute to the necessary variables.

user@debian:~$ sum=3+6 user@debian:~$ echo $sum 3+6 user@debian:~$ sum=$((3+6)) user@debian:~$ echo $sum 9 user@debian:~$ a=11 user@debian:~$ b=3 user@debian:~$ echo $a 11 user@debian:~$ echo $b 3 user@debian:~$ c=$a+$b user@debian:~$ echo $c 11+3 user@debian:~$ c=$(($a+$b)) user@debian:~$ echo $c 14 user@debian:~$ c=$((5)) user@debian:~$ c=5 user@debian:~$ d=10 user@debian:~$ e=$(($a+$b*$c-$d)) user@debian:~$ echo $e 16 user@debian:~$ sum=$((3+hello)) user@debian:~$ echo $sum 3 

Below I have repeated the above, except this time the integer attribute is first added to the sum, c and e variables. The results show Bash can evaluate arithmetic expressions without having to employ arithmetic expansion.

user@debian:~$ declare -i sum c e user@debian:~$ sum=3+6 user@debian:~$ echo $sum 9 user@debian:~$ a=11 user@debian:~$ b=3 user@debian:~$ c=a+b user@debian:~$ echo $c 14 user@debian:~$ c=5 user@debian:~$ d=10 user@debian:~$ e=a+b*c-d user@debian:~$ echo $e 16 user@debian:~$ sum=3+hello user@debian:~$ echo $sum 3 

Note that since the variable hello does not exist, a value of 0 is substituted.

3
  • What's the purpose of the closing parenthesis in read -d . DEBMAJVERS < /etc/debian_version)? Commented Aug 5 at 12:22
  • @U.Windl: I corrected the script by removing the closing parenthesis. Commented Aug 5 at 17:52
  • @Milliways: I removed my reference to MRE. Commented Aug 6 at 21:14
0

From your recent answer it seems like the code is working properly, to make it cleaner you can use this awk command to directly give you the first part of the version of debian

awk '{split($1,a,"."); print a[1]}' /etc/debian_version 

this gives an easier approach rather than printing both the major and minor version and then dissecting them in your script

3
  • In an earlier version of this I was using exactly this, but having already derived the full version running another awk seems less efficient. Commented Aug 4 at 11:24
  • Running an extra process just to strip off the fractional part is inefficient, however. The shell solution should be much faster. Commented Aug 5 at 12:24
  • and if you do run awk, you might as well just go with awk -F. '{print $1}' (you'd need split() if you need split on two different delimiters, like to get the 4 from 1.2.3 4.5) Commented Aug 6 at 10:36

You must log in to answer this question.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.