Compare 2 Strings

I have 2 values V_1_4_4_b1 and V_1_5_1_RC_b1. I would need to compare them and determine if the 1st value is greater, less or equal
than the 2nd value. The result should need to have a return value.
I have below code in bash function but it seems it is not comparing it correctly. Any help will be greatly appreciated. Thanks.

 checkversion ()
{
  local mod="$1"
  local vers="$2"
  local slink="/app/mes_dwh/$mod/${mod}"
  local target1="$(readlink "$slink" | cut -d 'V' -f2)"
  local addstring="V"
  local target="$addstring$target1"
   case "$target" in
        V_[0-9]_[0-9]_[0-9]_RC_b* | \
        V_[0-9]_[0-9]_[0-9]_b* ) ;;
        *) fail "bad version string: '$target'";;
  esac
  
  #compare major version
  local old="$( echo "$target" | cut -d_ -f2 )"
  local new="$( echo "$vers" | cut -d_ -f2 )"
  test "$old" -gt "$new" && return 77
   #compare middle version
  old="$( echo "$target" | cut -d_ -f3 )"
  new="$( echo "$vers" | cut -d_ -f3 )"
  test "$old" -gt "$new" && return 77
   #compare minor version
  old="$( echo "$target" | cut -d_ -f4 )"
  new="$( echo "$vers" | cut -d_ -f4 )"
  test "$old" -gt "$new" && return 77
   #compare build version
  old="$( echo "$target" | cut -d_ -f5 )"
  new="$( echo "$vers" | cut -d_ -f5 )" 
 
  case "${old}_${new}" in
  b*_RC) return 77;;
  b*_b*)
     #compare build level of final version (with 'b' stripped off
     old="${old#b}"
     new="${new#b}"
     test "$old" -gt "$new"  && return 77
     ;;
  RC_RC)
     #compare level of RC version (with the 'b' stripped off)
    old="$( echo "$target" | cut -d_ -f6 | cut -c2- )"
    new="$(echo "$vers" | cut -d_ -f6 | cut -c2-)"
    test "$old" -gt "$new" && return 77
    ;;
  RC_b*) return 0;;
  *) fail "unrecognized pattern";;
  esac
  
   return 0
 }
 

You could strip all but the numbers from the strings first and then compare like this example:

$ echo V_1_4_4_b1 V_1_5_1_RC_b1 | tr -d '[:alpha:]_'
1441 1511

How would RC / non RC compare?

By the script it seems rather static, but good point.

Just an additional information. One rule in the comparison. b is always greater than RC. But this rule will change if the first 4 numbers of the version is greater than the old one. Do you have any suggestion, if it is ok I will have the comparison 1 by 1?

With V_1_4_4_b1 , what would be the "first 4 numbers"?

A more versatile method for comparing version strings:

printf "%s\n" V_1_4_4_b1 V_1_5_1_RC_b1 | awk -F'[^[:digit:]]+' '{sum=0; for(i=1; i<=NF; i++) sum=sum*1000+$i; print sum}'

Assumption is that each number is 0..999.

First note that unlike C functions, shell functions can't return negative values. (The exit code from a shell function is a value in the range 0 through 255, inclusive.) Looking at your original script, it uses only two return values: 0 and 77. However, I don't know what the calls to fail do in your script. If they exit, that would exit the shell script that calls your function instead of just returning an error code for your function.

The following replacement for your function assumes that fail is a function that prints a diagnostic message and returns to the caller. (So after calling fail, this version of your function returns a non-zero exit status indicating that the checkversion() function was not able to successfully compare to version strings.) If this version of checkversion() does successfully compare two version strings it returns 0 and sets the variable checkversion_result to an integral value that is zero if the versions are the same, a value greater than zero if the version found in the symbolic link identified by the 1st operand is greater than the version passed in as the 2nd operand, or a value less than zero if the version found in the symlink is less than the version given as the 2nd operand.

All of the command substitutions using cut to find various subfields have been replaced by parameter substitutions (so this should be considerably faster). Debugging printf statements have been added so you can see each test being performed and the values used to determine the final result that is returned. (If the code is giving you values that are appropriate for what you're trying to do, you can remove all of the printf statements that start at the left margin. Note that I am not at all sure that I have the results correct in one, and only one, of the version strings contains RC_ .)

This should all work with any shell that performs the parameter expansions required by the POSIX standards. (It has been tested with bash and ksh .) Like your script, it will only work on a system that includes a readlink utility. (The readlink utility is provided by many systems, but is not required by the POSIX standards.)

checkversion_result=

checkversion() {
	checkversion_result=
	local mod="$1"
	local vers="$2"
	local slink="/app/mes_dwh/$mod/${mod}"
	local target=$(readlink "$slink")
	target=${target#*V}
	target=V${target%%V*}

	case "$target" in
        (V_[0-9]_[0-9]_[0-9]_RC_b* | V_[0-9]_[0-9]_[0-9]_b*)
		;;
        (*)	fail "bad version string: '$target'"
		return 1
		;;
	esac
	case "${target##*b}" in
        (*[!0-9]*)
        	fail "bad build number: '$target'"
		return 2
		;;
	esac
  
	#compare major version
	local old=${target#*_}
	old=${old%%_*}
	local new=${vers#*_}
	new=${new%%_*}
	checkversion_result=$((old - new))
printf 'major old:%s new:%s result:%s\n' "$old" "$new" "$checkversion_result"
	[ "$checkversion_result" -ne 0 ] && return 0
	#compare middle version
	old=${target#*_*_}
	old=${old%%_*}
	new=${vers#*_*_}
	new=${new%%_*}
	checkversion_result=$((old - new))
printf 'middle old:%s new:%s result:%s\n' "$old" "$new" "$checkversion_result"
	[ "$checkversion_result" -ne 0 ] && return 0
	#compare minor version
	old=${target#*_*_*_}
	old=${old%%_*}
	new=${vers#*_*_*_}
	new=${new%%_*}
	checkversion_result=$((old - new))
printf 'minor old:%s new:%s result:%s\n' "$old" "$new" "$checkversion_result"
	[ "$checkversion_result" -ne 0 ] && return 0
	#ocmpare RC_b versus b
	old=${target#*_*_*_*_}
	old=${old%%b*}
	new=${vers#*_*_*_*_}
	new=${new%%b*}
	if [ "$old" != "$new" ]
	then	[ "$old" = "" ] && checkversion_result=1 || \
				   checkversion_result=-1
	fi
printf 'RC old:%s new:%s result:%s\n' "$old" "$new" "$checkversion_result"
	[ "$checkversion_result" -ne 0 ] && return 0
	#compare build version
	old=${target##*b}
	new=${vers##*b}
	checkversion_result=$((old - new))
printf 'build old:%s new:%s result:%s\n' "$old" "$new" "$checkversion_result"
	return 0
}

The way to use this function would be something like:

if checkversion mod version
then	echo "version check completed successfully, result: $checkversion_result"
else	echo "version check failed; see diagnostics above"
fi

Hi.

Here is an adaptation of a code from stackoverflow . It has a debugger so that you can see details of each sub-comparison, so it's really a verbose option that you can change in the code. I would probably do it a little differently, but this code is already completed and works as shown below:

#!/usr/bin/env bash

# @(#) s2       Demonstrate version strings comparison.

LC_ALL=C ; LANG=C ; export LC_ALL LANG
pe() { for _i;do printf "%s" "$_i";done; printf "\n"; }
pl() { pe;pe "-----" ;pe "$*"; }
db() { ( printf " db, ";for _i;do printf "%s" "$_i";done;printf "\n" ) >&2 ; }
db() { : ; }
C=$HOME/bin/context && [ -f $C ] && $C

FILE=${1-data1}

pl " Input data file $FILE:"
head $FILE

pl " Version comparison script:"
cat vercomp

# Compare, look at exit status.
# return 1 for $1 > $2
# return 2 for $1 < $2
# return 0 for equal

pl " Results:"
while read v1 v2
do

./vercomp "$v1" "$v2"
v0=$?
case $v0 in
        (0) pe " Versions $v1 and $v2 are the equal." ;;
        (1) pe " $v1 is more recent than $v2." ;;
        (2) pe " $v2 is more recent than $v1." ;;
        (*) pe " Internal error." ;;
esac
done < $FILE

exit 0

producing:

$ ./s2

Environment: LC_ALL = C, LANG = C
(Versions displayed with local utility "version")
OS, ker|rel, machine: Linux, 3.16.0-4-amd64, x86_64
Distribution        : Debian 8.4 (jessie) 
bash GNU bash 4.3.30

-----
 Input data file data1:
V_1_4_4_b1      V_1_5_1_RC_b1
1.2-r3  1.2-r4
1.2rc3  1.2r4
3.14a   3.14b

-----
 Version comparison script:
#!/usr/bin/env bash

# @(#) vercomp  Compare version strings.
# Adapted from:
# http://stackoverflow.com/questions/4023830/bash-how-compare-two-strings-in-version-format

ascii_frag() {
  expr match "$1" "\([^[:digit:]]*\)"
}

ascii_remainder() {
  expr match "$1" "[^[:digit:]]*\(.*\)"
}

numeric_frag() {
  expr match "$1" "\([[:digit:]]*\)"
}

numeric_remainder() {
  expr match "$1" "[[:digit:]]*\(.*\)"
}

# Disable/enable debugging here.
vercomp_debug() {
  OUT="$1"
  # echo "${OUT}" >&2
}

# return 1 for $1 > $2
# return 2 for $1 < $2
# return 0 for equal
vercomp() {
  local WORK1="$1"
  local WORK2="$2"
  local NUM1="", NUM2="", ASCII1="", ASCII2=""
  while true; do
    vercomp_debug "ASCII compare"
    ASCII1=`ascii_frag "${WORK1}"`
    ASCII2=`ascii_frag "${WORK2}"`
    WORK1=`ascii_remainder "${WORK1}"`
    WORK2=`ascii_remainder "${WORK2}"`
    vercomp_debug "\"${ASCII1}\" remainder \"${WORK1}\""
    vercomp_debug "\"${ASCII2}\" remainder \"${WORK2}\""
    
    if [ "${ASCII1}" \> "${ASCII2}" ]; then
      vercomp_debug "ascii ${ASCII1} > ${ASCII2}"
      return 1
      elif [ "${ASCII1}" \< "${ASCII2}" ]; then
      vercomp_debug "ascii ${ASCII1} < ${ASCII2}"
      return 2
    fi
    vercomp_debug "--------"
    
    vercomp_debug "Numeric compare"
    NUM1=`numeric_frag "${WORK1}"`
    NUM2=`numeric_frag "${WORK2}"`
    WORK1=`numeric_remainder "${WORK1}"`
    WORK2=`numeric_remainder "${WORK2}"`
    vercomp_debug "\"${NUM1}\" remainder \"${WORK1}\""
    vercomp_debug "\"${NUM2}\" remainder \"${WORK2}\""
    
    if [ -z "${NUM1}" -a -z "${NUM2}" ]; then
      vercomp_debug "blank 1 and blank 2 equal"
      return 0
      elif [ -z "${NUM1}" -a -n "${NUM2}" ]; then
      vercomp_debug "blank 1 less than non-blank 2"
      return 2
      elif [ -n "${NUM1}" -a -z "${NUM2}" ]; then
      vercomp_debug "non-blank 1 greater than blank 2"
      return 1
    fi
    
    if [ "${NUM1}" -gt "${NUM2}" ]; then
      vercomp_debug "num ${NUM1} > ${NUM2}"
      return 1
      elif [ "${NUM1}" -lt "${NUM2}" ]; then
      vercomp_debug "num ${NUM1} < ${NUM2}"
      return 2
    fi
    vercomp_debug "--------"
  done
}

vercomp "$@"

-----
 Results:
 V_1_5_1_RC_b1 is more recent than V_1_4_4_b1.
 1.2-r4 is more recent than 1.2-r3.
 1.2rc3 is more recent than 1.2r4.
 3.14b is more recent than 3.14a.

I tested it as shown, which is not extensively, only to see if it seems to basically work.

Best wishes ... cheers, drl