1

I'm trying to determine which version of DB2 is the most recent on a given node. We install our DB2 software in the /opt/IBM/db2 directory. If I list that directory I get

V10.5
V9.1
V9.5
V9.5fp10
V9.7
V9.7fp3
V9.7fp6
V9.7fp7

Obviously 10.5 is currently the latest version, but the first entry won't always be the latest (ie: when we install V11.0). Is there a relatively easy way to determine the latest (in ksh93)? I could parse each entry out into major/minor/fixpack but that seems labour intensive.

  • 3
    easiest probably sort -V in GNU sort – ilkkachu Dec 6 '17 at 20:58
  • To avoid confusion, your toe says “sort” but are you really after the single newest directory/version? – Jeff Schaller Dec 7 '17 at 16:00
  • Auto-correction fail; I meant "your title says sort" but ... – Jeff Schaller Dec 10 '17 at 2:17
3

If you have GNU tools, you could use ls -v

Or, use perl:

printf "%s\n" * | perl -e '
    @sorted = map {$_->[1]}
              sort {$a->[0] <=> $b->[0] or $a->[1] cmp $b->[1]}
              map {/(\d+\.\d*)/ and [$1, $_]}
              <>;
    print $sorted[-1];
'
| improve this answer | |
  • +1 for pointing out the ls -v option that has been hiding in plain sight all this time. – Timothy Martin Dec 6 '17 at 22:13
  • Thanks for all the replies. We don't have GNU tools installed, but that prompted more questions. Instead of asking them here I started this separate thread (unix.stackexchange.com/questions/409508/…). – Scavenger Dec 7 '17 at 15:41
  • The perl one sorts V10.10 before V10.5 though. – Stéphane Chazelas Dec 7 '17 at 16:23
3

Without GNU tools, and just the labor-intensive shell scripting:

recentdb2 () (
  cd /opt/IBM/db2 || return 1
  highest=
  himajor=
  himinor=
  hifp=

  for dir in V*.*
  do
        [ ! -d "$dir" ] && continue
        nov="${dir#V*}"
        major="${nov%%.*}"
        notmajor="${nov##*.}"
        minor="${notmajor%%fp*}"
        if [ "$minor" = "$notmajor" ]
        then
                # no FP
                fp=0
        else
                fp="${notmajor##*fp}"
        fi

        # if highest major isn't set, set it and continue
        # else compare major; if higher, set it and continue
        # else compare minor; if higher, set it and continue
        # else compare fp; if higher, set it

        if [ "${himajor:-notset}" = "notset" ]
        then
                highest="$dir"
                himajor="$major"
                himinor="$minor"
                hifp="$fp"
                continue
        fi

        if [ "$major" -gt "$himajor" ]
        then
                highest="$dir"
                himajor="$major"
                himinor="$minor"
                hifp="$fp"
                continue
        elif [ "$major" -eq "$himajor" ] && [ "$minor" -gt "$himinor" ]
        then
                highest="$dir"
                himajor="$major"
                himinor="$minor"
                hifp="$fp"
                continue
        elif [ "$major" -eq "$himajor" ] && [ "$minor" -eq "$himinor" ] && [ "$fp" -gt "$hifp" ]
        then
                highest="$dir"
                himajor="$major"
                himinor="$minor"
                hifp="$fp"
        fi
        # else, the current value is less than the highest value, drop it and continue on
  done
  printf "%s" "$highest"
)

This defines a function that will (attempt) to return the highest-level DB2 directory from /opt/IBM/db2.

The function all runs within a subshell, so that:

  • the variables that it creates disappear when it completes, and
  • the cd is also isolated to the subshell

The function then loops over the entries in the /opt/IBM/db2 directory that match the glob pattern V*.* -- adjust this if you could have versions such as V11 without any dots. The first test is to ensure that we are not fooled by a stray file matching this pattern.

The labor begins: we strip the leading V off ("no V"), then compute:

  • the major number is everything before the first period
  • the "not major number" is everything after the first period
  • the minor number is the part from the "not major number" up until any fp
  • if there is an fp in the name (there was no change from the full "not major number" string to the minor number), then set the fp to that, otherwise set it to zero

As the comments then say, we test and set the "high" variables appropriately. The first directory we encounter will enter the first condition -- where it's the highest level by default.

Subsequent directory entries are then compared against the currently-highest major, minor, and fp versions.

Once the loop is complete, the function prints out the most recent directory name.

Use the function by creating a script or sourcing in the code, then call the function:

h=$(recentdb2)
| improve this answer | |
2

With zsh:

list=(/opt/IBM/db2/V*(Nn:t))

where the n glob qualifier enables numeric sorting which does exactly what you want, here sort from oldest ($list[1]) to newest ($list[-1]).

With ksh93, if it has been built with the ls builtin from ast-open you could use its version sort:

(cd /opt/IBM/db2 && command /opt/ast/bin/ls -d -y version V*)

command /opt/ast/bin/ls invokes the /opt/ast/bin/ls command or the corresponding builtin equivalent if present, regardless of whether /opt/ast/bin/ls exists or not on the file system.

You can also do:

PATH=/opt/ast/bin:$PATH
ls -d -y version V*

To make that ls builtin take precedence over the system's one in /bin or /usr/bin.

Otherwise, you could do:

tmp=(/opt/IBM/db2/~(N)V*) # ~(N) equivalent of zsh's N
tmp=("${tmp[@]##*/}")     # equivalent of :t
typeset -A map

# build an associative array where the key is the element with
# all the numbers in them 0-padded to 20 digits. Append a counter
# to make sure all keys are unique.
typeset -Z 20 n=0
for i in "${tmp[@]}"; do
  k=${i//+([0-9])/00000000000000000000\1}
  k=${k//+(0){20}([0-9])/\2}
  map[$k-$n]=$i
  ((n++))
done

# sort the keys of the associative array into $@:
set -s -- "${!map[@]}"

# fill $list with the values for those (now sorted) keys.
typeset -a list
for i do
  list+=("${map[$i]}")
done
printf -- '- %s\n' "${list[@]}"
((${#list[@]})) && printf 'Newest: %s\n' "${list[@]: -1}"

On your sample, that gives:

- V9.1
- V9.5
- V9.5fp10
- V9.7
- V9.7fp3
- V9.7fp6
- V9.7fp7
- V10.5
Newest: V10.5
| improve this answer | |

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

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