IF Condition for intervals made by real numbers in bash - bash

This is a bash routine to compare two numbers with some defined intervals given by integers numbers:
#!/bin/bash
# The comparing function
function compareInterval {
t1=$1
t2=$2
shift 2
while (( "$2" )); do
if (( $t1 >= $1 && $t2 <= $2 )); then
# got match
return 0
fi
shift 2
done
return 1
}
# sample values
t_initial=2
t_final=4
# Invocation. Compares against 1-3, 3-5, 2-5
if compareInterval $t_initial $t_final 1 3 3 5 2 5; then
echo Got match
fi
If the intervals are given by real numbers, i.e., 1.234, how does the condition in the function change?

Here's a new version of the code:
#!/bin/bash
function compareInterval {
t1=$1
t2=$2
shift 2
while (( $(awk -v var="$2" 'BEGIN{ if (var=="") print 0; else print 1; }') )); do
var1=$(awk -v t1="$t1" -v t2="$1" 'BEGIN{ print (t1 >= t2) }')
var2=$(awk -v t3="$t2" -v t4="$2" 'BEGIN{ print (t3 <= t4) }')
if [[ "$var1" -eq "1" && "$var2" -eq "1" ]]; then
# got match
return 0
fi
shift 2
done
return 1
}
t_initial=4399.75148230007220954256
t_final=4399.75172111932808454256
if compareInterval $t_initial $t_final 4399.48390124308 4400.47652912846 3 5 2 5; then
echo Got match
fi

Just another pure bash solution:
#!/bin/bash
function compareInterval {
t1=$1 t2=$2
shift 2
while [[ $# -ge 2 ]]; do
is_ge "$t1" "$1" && is_le "$t2" "$2" && return 0 ## Got match.
shift 2
done
return 1
}
function is_ge {
local A1 A2 B1 B2
if [[ $1 == *.* ]]; then
A1=${1%%.*}
A2=${1##*.}
else
A1=$1
A2=0
fi
if [[ $2 == *.* ]]; then
B1=${2%%.*}
B2=${2##*.}
else
B1=$2
B2=0
fi
(( L = ${#A2} > ${#B2} ? ${#A2} : ${#B2} ))
A2=$A2'00000000000000000000'; A2=1${A2:0:L}
B2=$B2'00000000000000000000'; B2=1${B2:0:L}
(( A1 == B1 ? A2 >= B2 : A1 > B1 ))
}
function is_le {
local A1 A2 B1 B2
if [[ $1 == *.* ]]; then
A1=${1%%.*}
A2=${1##*.}
else
A1=$1
A2=0
fi
if [[ $2 == *.* ]]; then
B1=${2%%.*}
B2=${2##*.}
else
B1=$2
B2=0
fi
(( L = ${#A2} > ${#B2} ? ${#A2} : ${#B2} ))
A2=$A2'00000000000000000000'; A2=1${A2:0:L}
B2=$B2'00000000000000000000'; B2=1${B2:0:L}
(( A1 == B1 ? A2 <= B2 : A1 < B1 ))
}
t_initial=2.4
t_final=4.5
if compareInterval "$t_initial" "$t_final" 1 3 3 5 2 5; then
echo 'Got match.'
fi
Note: Of course sanity checks can be added but I find that not too necessary for now.

Related

How to check version number with letter in it? [duplicate]

Is there any way to compare such strings on bash, e.g.: 2.4.5 and 2.8 and 2.4.5.1?
Here is a pure Bash version that doesn't require any external utilities:
#!/bin/bash
vercomp () {
if [[ $1 == $2 ]]
then
return 0
fi
local IFS=.
local i ver1=($1) ver2=($2)
# fill empty fields in ver1 with zeros
for ((i=${#ver1[#]}; i<${#ver2[#]}; i++))
do
ver1[i]=0
done
for ((i=0; i<${#ver1[#]}; i++))
do
if [[ -z ${ver2[i]} ]]
then
# fill empty fields in ver2 with zeros
ver2[i]=0
fi
if ((10#${ver1[i]} > 10#${ver2[i]}))
then
return 1
fi
if ((10#${ver1[i]} < 10#${ver2[i]}))
then
return 2
fi
done
return 0
}
testvercomp () {
vercomp $1 $2
case $? in
0) op='=';;
1) op='>';;
2) op='<';;
esac
if [[ $op != $3 ]]
then
echo "FAIL: Expected '$3', Actual '$op', Arg1 '$1', Arg2 '$2'"
else
echo "Pass: '$1 $op $2'"
fi
}
# Run tests
# argument table format:
# testarg1 testarg2 expected_relationship
echo "The following tests should pass"
while read -r test
do
testvercomp $test
done << EOF
1 1 =
2.1 2.2 <
3.0.4.10 3.0.4.2 >
4.08 4.08.01 <
3.2.1.9.8144 3.2 >
3.2 3.2.1.9.8144 <
1.2 2.1 <
2.1 1.2 >
5.6.7 5.6.7 =
1.01.1 1.1.1 =
1.1.1 1.01.1 =
1 1.0 =
1.0 1 =
1.0.2.0 1.0.2 =
1..0 1.0 =
1.0 1..0 =
EOF
echo "The following test should fail (test the tester)"
testvercomp 1 1 '>'
Run the tests:
$ . ./vercomp
The following tests should pass
Pass: '1 = 1'
Pass: '2.1 < 2.2'
Pass: '3.0.4.10 > 3.0.4.2'
Pass: '4.08 < 4.08.01'
Pass: '3.2.1.9.8144 > 3.2'
Pass: '3.2 < 3.2.1.9.8144'
Pass: '1.2 < 2.1'
Pass: '2.1 > 1.2'
Pass: '5.6.7 = 5.6.7'
Pass: '1.01.1 = 1.1.1'
Pass: '1.1.1 = 1.01.1'
Pass: '1 = 1.0'
Pass: '1.0 = 1'
Pass: '1.0.2.0 = 1.0.2'
Pass: '1..0 = 1.0'
Pass: '1.0 = 1..0'
The following test should fail (test the tester)
FAIL: Expected '>', Actual '=', Arg1 '1', Arg2 '1'
If you have coreutils-7 (in Ubuntu Karmic but not Jaunty) then your sort command should have a -V option (version sort) which you could use to do the comparison:
verlte() {
[ "$1" = "`echo -e "$1\n$2" | sort -V | head -n1`" ]
}
verlt() {
[ "$1" = "$2" ] && return 1 || verlte $1 $2
}
verlte 2.5.7 2.5.6 && echo "yes" || echo "no" # no
verlt 2.4.10 2.4.9 && echo "yes" || echo "no" # no
verlt 2.4.8 2.4.10 && echo "yes" || echo "no" # yes
verlte 2.5.6 2.5.6 && echo "yes" || echo "no" # yes
verlt 2.5.6 2.5.6 && echo "yes" || echo "no" # no
There probably is no universally correct way to achieve this. If you are trying to compare versions in the Debian package system try dpkg --compare-versions <first> <relation> <second>.
GNU sort has an option for it:
printf '2.4.5\n2.8\n2.4.5.1\n' | sort -V
gives:
2.4.5
2.4.5.1
2.8
function version { echo "$#" | awk -F. '{ printf("%d%03d%03d%03d\n", $1,$2,$3,$4); }'; }
Used as such:
if [ $(version $VAR) -ge $(version "6.2.0") ]; then
echo "Version is up to date"
fi
(from https://apple.stackexchange.com/a/123408/11374)
Well if you know the number of fields you can use -k n,n and get a super-simple solution
echo '2.4.5
2.8
2.4.5.1
2.10.2' | sort -t '.' -k 1,1 -k 2,2 -k 3,3 -k 4,4 -g
2.4.5
2.4.5.1
2.8
2.10.2
This is for at most 4 fields in the version.
$ function ver { printf "%03d%03d%03d%03d" $(echo "$1" | tr '.' ' '); }
$ [ $(ver 10.9) -lt $(ver 10.10) ] && echo hello
hello
You can recursively split on . and compare as shown in the following algorithm, taken from here. It returns 10 if the versions are the same, 11 if version 1 is greater than version 2 and 9 otherwise.
#!/bin/bash
do_version_check() {
[ "$1" == "$2" ] && return 10
ver1front=`echo $1 | cut -d "." -f -1`
ver1back=`echo $1 | cut -d "." -f 2-`
ver2front=`echo $2 | cut -d "." -f -1`
ver2back=`echo $2 | cut -d "." -f 2-`
if [ "$ver1front" != "$1" ] || [ "$ver2front" != "$2" ]; then
[ "$ver1front" -gt "$ver2front" ] && return 11
[ "$ver1front" -lt "$ver2front" ] && return 9
[ "$ver1front" == "$1" ] || [ -z "$ver1back" ] && ver1back=0
[ "$ver2front" == "$2" ] || [ -z "$ver2back" ] && ver2back=0
do_version_check "$ver1back" "$ver2back"
return $?
else
[ "$1" -gt "$2" ] && return 11 || return 9
fi
}
do_version_check "$1" "$2"
Source
Function V - pure bash solution, no external utilities required.
Supports = == != < <= > and >= (lexicographic).
Optional tail letter comparison: 1.5a < 1.5b
Unequal length comparison: 1.6 > 1.5b
Reads left-to-right: if V 1.5 '<' 1.6; then ....
<>
# Sample output
# Note: ++ (true) and __ (false) mean that V works correctly.
++ 3.6 '>' 3.5b
__ 2.5.7 '<=' 2.5.6
++ 2.4.10 '<' 2.5.9
__ 3.0002 '>' 3.0003.3
++ 4.0-RC2 '>' 4.0-RC1
<>
function V() # $1-a $2-op $3-$b
# Compare a and b as version strings. Rules:
# R1: a and b : dot-separated sequence of items. Items are numeric. The last item can optionally end with letters, i.e., 2.5 or 2.5a.
# R2: Zeros are automatically inserted to compare the same number of items, i.e., 1.0 < 1.0.1 means 1.0.0 < 1.0.1 => yes.
# R3: op can be '=' '==' '!=' '<' '<=' '>' '>=' (lexicographic).
# R4: Unrestricted number of digits of any item, i.e., 3.0003 > 3.0000004.
# R5: Unrestricted number of items.
{
local a=$1 op=$2 b=$3 al=${1##*.} bl=${3##*.}
while [[ $al =~ ^[[:digit:]] ]]; do al=${al:1}; done
while [[ $bl =~ ^[[:digit:]] ]]; do bl=${bl:1}; done
local ai=${a%$al} bi=${b%$bl}
local ap=${ai//[[:digit:]]} bp=${bi//[[:digit:]]}
ap=${ap//./.0} bp=${bp//./.0}
local w=1 fmt=$a.$b x IFS=.
for x in $fmt; do [ ${#x} -gt $w ] && w=${#x}; done
fmt=${*//[^.]}; fmt=${fmt//./%${w}s}
printf -v a $fmt $ai$bp; printf -v a "%s-%${w}s" $a $al
printf -v b $fmt $bi$ap; printf -v b "%s-%${w}s" $b $bl
case $op in
'<='|'>=' ) [ "$a" ${op:0:1} "$b" ] || [ "$a" = "$b" ] ;;
* ) [ "$a" $op "$b" ] ;;
esac
}
Code Explained
Line 1: Define local variables:
a, op, b - comparison operands and operator, i.e., "3.6" > "3.5a".
al, bl - letter tails of a and b, initialized to the tail item, i.e., "6" and "5a".
Lines 2, 3: Left-trim digits from the tail items so only letters are left, if any, i.e., "" and "a".
Line 4: Right trim letters from a and b to leave just the sequence of numeric items as local variables ai and bi, i.e., "3.6" and "3.5".
Notable example: "4.01-RC2" > "4.01-RC1" yields ai="4.01" al="-RC2" and bi="4.01" bl="-RC1".
Line 6: Define local variables:
ap, bp - zero right-paddings for ai and bi. Start by keeping the inter-item dots only, of which number equals the number of elements of a and b respectively.
Line 7: Then append "0" after each dot to make padding masks.
Line 9: Local variables:
w - item width
fmt - printf format string, to be calculated
x - temporary
With IFS=. bash splits variable values at '.'.
Line 10: Calculate w, the maximum item width, which will be used to align items for lexicographic comparison. In our example w=2.
Line 11: Create the printf alignment format by replacing each character of $a.$b with %${w}s, i.e., "3.6" > "3.5a" yields "%2s%2s%2s%2s".
Line 12: "printf -v a" sets the value of variable a. This is equivalent to a=sprintf(...) in many programming languages. Note that here, by effect of IFS=. the arguments to printf split into individual items.
With the first printf items of a are left-padded with spaces while enough "0" items are appended from bp to ensure that the resulting string a can be meaningfully compared to a similarly formatted b.
Note that we append bp - not ap to ai because ap and bp may have different lenghts, so this results in a and b having equal lengths.
With the second printf we append the letter part al to a with enough padding to enable meaningful comparison. Now a is ready for comparison with b.
Line 13: Same as line 12 but for b.
Line 15: Split comparison cases between non-built-in (<= and >=) and built-in operators.
Line 16: If the comparison operator is <= then test for a<b or a=b - respectively >= a<b or a=b
Line 17: Test for built-in comparison operators.
<>
# All tests
function P { printf "$#"; }
function EXPECT { printf "$#"; }
function CODE { awk $BASH_LINENO'==NR{print " "$2,$3,$4}' "$0"; }
P 'Note: ++ (true) and __ (false) mean that V works correctly.\n'
V 2.5 '!=' 2.5 && P + || P _; EXPECT _; CODE
V 2.5 '=' 2.5 && P + || P _; EXPECT +; CODE
V 2.5 '==' 2.5 && P + || P _; EXPECT +; CODE
V 2.5a '==' 2.5b && P + || P _; EXPECT _; CODE
V 2.5a '<' 2.5b && P + || P _; EXPECT +; CODE
V 2.5a '>' 2.5b && P + || P _; EXPECT _; CODE
V 2.5b '>' 2.5a && P + || P _; EXPECT +; CODE
V 2.5b '<' 2.5a && P + || P _; EXPECT _; CODE
V 3.5 '<' 3.5b && P + || P _; EXPECT +; CODE
V 3.5 '>' 3.5b && P + || P _; EXPECT _; CODE
V 3.5b '>' 3.5 && P + || P _; EXPECT +; CODE
V 3.5b '<' 3.5 && P + || P _; EXPECT _; CODE
V 3.6 '<' 3.5b && P + || P _; EXPECT _; CODE
V 3.6 '>' 3.5b && P + || P _; EXPECT +; CODE
V 3.5b '<' 3.6 && P + || P _; EXPECT +; CODE
V 3.5b '>' 3.6 && P + || P _; EXPECT _; CODE
V 2.5.7 '<=' 2.5.6 && P + || P _; EXPECT _; CODE
V 2.4.10 '<' 2.4.9 && P + || P _; EXPECT _; CODE
V 2.4.10 '<' 2.5.9 && P + || P _; EXPECT +; CODE
V 3.4.10 '<' 2.5.9 && P + || P _; EXPECT _; CODE
V 2.4.8 '>' 2.4.10 && P + || P _; EXPECT _; CODE
V 2.5.6 '<=' 2.5.6 && P + || P _; EXPECT +; CODE
V 2.5.6 '>=' 2.5.6 && P + || P _; EXPECT +; CODE
V 3.0 '<' 3.0.3 && P + || P _; EXPECT +; CODE
V 3.0002 '<' 3.0003.3 && P + || P _; EXPECT +; CODE
V 3.0002 '>' 3.0003.3 && P + || P _; EXPECT _; CODE
V 3.0003.3 '<' 3.0002 && P + || P _; EXPECT _; CODE
V 3.0003.3 '>' 3.0002 && P + || P _; EXPECT +; CODE
V 4.0-RC2 '>' 4.0-RC1 && P + || P _; EXPECT +; CODE
V 4.0-RC2 '<' 4.0-RC1 && P + || P _; EXPECT _; CODE
if it's just about to know whether one version is lower than another I came up checking whether sort --version-sort changes the order of my version strings:
string="$1
$2"
[ "$string" == "$(sort --version-sort <<< "$string")" ]
$ for OVFTOOL_VERSION in "4.2.0" "4.2.1" "5.2.0" "3.2.0" "4.1.9" "4.0.1" "4.3.0" "4.5.0" "4.2.1" "30.1.0" "4" "5" "4.1" "4.3"
> do
> if [ $(echo "$OVFTOOL_VERSION 4.2.0" | tr " " "\n" | sort --version-sort | head -n 1) = 4.2.0 ]; then
> echo "$OVFTOOL_VERSION is >= 4.2.0";
> else
> echo "$OVFTOOL_VERSION is < 4.2.0";
> fi
> done
4.2.0 is >= 4.2.0
4.2.1 is >= 4.2.0
5.2.0 is >= 4.2.0
3.2.0 is < 4.2.0
4.1.9 is < 4.2.0
4.0.1 is < 4.2.0
4.3.0 is >= 4.2.0
4.5.0 is >= 4.2.0
4.2.1 is >= 4.2.0
30.1.0 is >= 4.2.0
4 is < 4.2.0
5 is >= 4.2.0
4.1 is < 4.2.0
4.3 is >= 4.2.0
I implemented a function that returns the same results as Dennis Williamson's but uses fewer lines. It does perform a sanity check initially which causes 1..0 to fail from his tests (which I would argue should be the case) but all of his other tests pass with this code:
#!/bin/bash
version_compare() {
if [[ $1 =~ ^([0-9]+\.?)+$ && $2 =~ ^([0-9]+\.?)+$ ]]; then
local l=(${1//./ }) r=(${2//./ }) s=${#l[#]}; [[ ${#r[#]} -gt ${#l[#]} ]] && s=${#r[#]}
for i in $(seq 0 $((s - 1))); do
[[ ${l[$i]} -gt ${r[$i]} ]] && return 1
[[ ${l[$i]} -lt ${r[$i]} ]] && return 2
done
return 0
else
echo "Invalid version number given"
exit 1
fi
}
Here is a simple Bash function that uses no external commands. It works for version strings that have up to three numeric parts in them - less than 3 is fine as well. It can easily be extended for more. It implements =, <, <=, >, >=, and != conditions.
#!/bin/bash
vercmp() {
version1=$1 version2=$2 condition=$3
IFS=. v1_array=($version1) v2_array=($version2)
v1=$((v1_array[0] * 100 + v1_array[1] * 10 + v1_array[2]))
v2=$((v2_array[0] * 100 + v2_array[1] * 10 + v2_array[2]))
diff=$((v2 - v1))
[[ $condition = '=' ]] && ((diff == 0)) && return 0
[[ $condition = '!=' ]] && ((diff != 0)) && return 0
[[ $condition = '<' ]] && ((diff > 0)) && return 0
[[ $condition = '<=' ]] && ((diff >= 0)) && return 0
[[ $condition = '>' ]] && ((diff < 0)) && return 0
[[ $condition = '>=' ]] && ((diff <= 0)) && return 0
return 1
}
Here is the test:
for tv1 in '*' 1.1.1 2.5.3 7.3.0 0.5.7 10.3.9 8.55.32 0.0.1; do
for tv2 in 3.1.1 1.5.3 4.3.0 0.0.7 0.3.9 11.55.32 10.0.0 '*'; do
for c in '=' '>' '<' '>=' '<=' '!='; do
vercmp "$tv1" "$tv2" "$c" && printf '%s\n' "$tv1 $c $tv2 is true" || printf '%s\n' "$tv1 $c $tv2 is false"
done
done
done
A subset of the test output:
<snip>
* >= * is true
* <= * is true
* != * is true
1.1.1 = 3.1.1 is false
1.1.1 > 3.1.1 is false
1.1.1 < 3.1.1 is true
1.1.1 >= 3.1.1 is false
1.1.1 <= 3.1.1 is true
1.1.1 != 3.1.1 is true
1.1.1 = 1.5.3 is false
1.1.1 > 1.5.3 is false
1.1.1 < 1.5.3 is true
1.1.1 >= 1.5.3 is false
1.1.1 <= 1.5.3 is true
1.1.1 != 1.5.3 is true
1.1.1 = 4.3.0 is false
1.1.1 > 4.3.0 is false
<snip>
This is also a pure bash solution, as printf is a bash builtin.
function ver()
# Description: use for comparisons of version strings.
# $1 : a version string of form 1.2.3.4
# use: (( $(ver 1.2.3.4) >= $(ver 1.2.3.3) )) && echo "yes" || echo "no"
{
printf "%02d%02d%02d%02d" ${1//./ }
}
I'm using embedded Linux (Yocto) with BusyBox. BusyBox sort doesn't have a -V option (but BusyBox expr match can do regular expressions). So I needed a Bash version compare which worked with that constraint.
I've made the following (similar to Dennis Williamson's answer) to compare using a "natural sort" type of algorithm. It splits the string into numeric parts and non-numeric parts; it compares the numeric parts numerically (so 10 is greater than 9), and compares the non-numeric parts as a plain ASCII comparison.
ascii_frag() {
expr match "$1" "\([^[:digit:]]*\)"
}
ascii_remainder() {
expr match "$1" "[^[:digit:]]*\(.*\)"
}
numeric_frag() {
expr match "$1" "\([[:digit:]]*\)"
}
numeric_remainder() {
expr match "$1" "[[:digit:]]*\(.*\)"
}
vercomp_debug() {
OUT="$1"
#echo "${OUT}"
}
# 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
}
It can compare more complicated version numbers such as
1.2-r3 versus 1.2-r4
1.2rc3 versus 1.2r4
Note that it doesn't return the same result for some of the corner-cases in Dennis Williamson's answer. In particular:
1 1.0 <
1.0 1 >
1.0.2.0 1.0.2 >
1..0 1.0 >
1.0 1..0 <
But those are corner cases, and I think the results are still reasonable.
You all gave complicated solutions. Here's a simpler one.
function compare_versions {
local a=${1%%.*} b=${2%%.*}
[[ "10#${a:-0}" -gt "10#${b:-0}" ]] && return 1
[[ "10#${a:-0}" -lt "10#${b:-0}" ]] && return 2
a=${1:${#a} + 1} b=${2:${#b} + 1}
[[ -z $a && -z $b ]] || compare_versions "$a" "$b"
}
Usage: compare_versions <ver_a> <ver_b>
Return code 1 means first version is greater than second, 2 means less, and 0 means both are equal.
Also a non-recursive version:
function compare_versions {
local a=$1 b=$2 x y
while [[ $a || $b ]]; do
x=${a%%.*} y=${b%%.*}
[[ "10#${x:-0}" -gt "10#${y:-0}" ]] && return 1
[[ "10#${x:-0}" -lt "10#${y:-0}" ]] && return 2
a=${a:${#x} + 1} b=${b:${#y} + 1}
done
return 0
}
Here's a pure Bash solution that supports revisions (e.g. '1.0-r1'), based on the answer posted by Dennis Williamson. It can easily be modified to support stuff like '-RC1' or extract the version from a more complex string by changing the regular expression.
For details regarding the implementation, please refer to in-code comments and/or enable the included debug code:
#!/bin/bash
# Compare two version strings [$1: version string 1 (v1), $2: version string 2 (v2)]
# Return values:
# 0: v1 == v2
# 1: v1 > v2
# 2: v1 < v2
# Based on: https://stackoverflow.com/a/4025065 by Dennis Williamson
function compare_versions() {
# Trivial v1 == v2 test based on string comparison
[[ "$1" == "$2" ]] && return 0
# Local variables
local regex="^(.*)-r([0-9]*)$" va1=() vr1=0 va2=() vr2=0 len i IFS="."
# Split version strings into arrays, extract trailing revisions
if [[ "$1" =~ ${regex} ]]; then
va1=(${BASH_REMATCH[1]})
[[ -n "${BASH_REMATCH[2]}" ]] && vr1=${BASH_REMATCH[2]}
else
va1=($1)
fi
if [[ "$2" =~ ${regex} ]]; then
va2=(${BASH_REMATCH[1]})
[[ -n "${BASH_REMATCH[2]}" ]] && vr2=${BASH_REMATCH[2]}
else
va2=($2)
fi
# Bring va1 and va2 to same length by filling empty fields with zeros
(( ${#va1[#]} > ${#va2[#]} )) && len=${#va1[#]} || len=${#va2[#]}
for ((i=0; i < len; ++i)); do
[[ -z "${va1[i]}" ]] && va1[i]="0"
[[ -z "${va2[i]}" ]] && va2[i]="0"
done
# Append revisions, increment length
va1+=($vr1)
va2+=($vr2)
len=$((len+1))
# *** DEBUG ***
#echo "TEST: '${va1[#]} (?) ${va2[#]}'"
# Compare version elements, check if v1 > v2 or v1 < v2
for ((i=0; i < len; ++i)); do
if (( 10#${va1[i]} > 10#${va2[i]} )); then
return 1
elif (( 10#${va1[i]} < 10#${va2[i]} )); then
return 2
fi
done
# All elements are equal, thus v1 == v2
return 0
}
# ---------- everything below this line is just for testing ----------
# Test compare_versions [$1: version string 1, $2: version string 2, $3: expected result]
function test_compare_versions() {
local op
compare_versions "$1" "$2"
case $? in
0) op="==" ;;
1) op=">" ;;
2) op="<" ;;
esac
if [[ "$op" == "$3" ]]; then
echo -e "\e[1;32mPASS: '$1 $op $2'\e[0m"
else
echo -e "\e[1;31mFAIL: '$1 $3 $2' (result: '$1 $op $2')\e[0m"
fi
}
echo -e "\nThe following tests should pass:"
while read -r test; do
test_compare_versions $test
done << EOF
1 1 ==
2.1 2.2 <
3.0.4.10 3.0.4.2 >
4.08 4.08.01 <
3.2.1.9.8144 3.2 >
3.2 3.2.1.9.8144 <
1.2 2.1 <
2.1 1.2 >
5.6.7 5.6.7 ==
1.01.1 1.1.1 ==
1.1.1 1.01.1 ==
1 1.0 ==
1.0 1 ==
1.0.2.0 1.0.2 ==
1..0 1.0 ==
1.0 1..0 ==
1.0-r1 1.0-r3 <
1.0-r9 2.0 <
3.0-r15 3.0-r9 >
...-r1 ...-r2 <
2.0-r1 1.9.8.21-r2 >
1.0 3.8.9.32-r <
-r -r3 <
-r3 -r >
-r3 -r3 ==
-r -r ==
0.0-r2 0.0.0.0-r2 ==
1.0.0.0-r2 1.0-r2 ==
0.0.0.1-r7 -r9 >
0.0-r0 0 ==
1.002.0-r6 1.2.0-r7 <
001.001-r2 1.1-r2 ==
5.6.1-r0 5.6.1 ==
EOF
echo -e "\nThe following tests should fail:"
while read -r test; do
test_compare_versions $test
done << EOF
1 1 >
3.0.5-r5 3..5-r5 >
4.9.21-r3 4.8.22-r9 <
1.0-r 1.0-r1 ==
-r 1.0-r >
-r1 0.0-r1 <
-r2 0-r2 <
EOF
For old version/busybox sort. Simple form provide roughly result and often works.
sort -n
This is escpecial useful on version which contains alpha symbols like
10.c.3
10.a.4
2.b.5
Here's a refinement of the top answer (Dennis's) that is more concise and uses a different return value scheme to make it easy to implement <= and >= with a single comparison. It also compares everything after the first character not in [0-9.] lexicographically, so 1.0rc1 < 1.0rc2.
# Compares two tuple-based, dot-delimited version numbers a and b (possibly
# with arbitrary string suffixes). Returns:
# 1 if a<b
# 2 if equal
# 3 if a>b
# Everything after the first character not in [0-9.] is compared
# lexicographically using ASCII ordering if the tuple-based versions are equal.
compare_versions() {
if [[ $1 == "$2" ]]; then
return 2
fi
local IFS=.
local i a=(${1%%[^0-9.]*}) b=(${2%%[^0-9.]*})
local arem=${1#${1%%[^0-9.]*}} brem=${2#${2%%[^0-9.]*}}
for ((i=0; i<${#a[#]} || i<${#b[#]}; i++)); do
if ((10#${a[i]:-0} < 10#${b[i]:-0})); then
return 1
elif ((10#${a[i]:-0} > 10#${b[i]:-0})); then
return 3
fi
done
if [ "$arem" '<' "$brem" ]; then
return 1
elif [ "$arem" '>' "$brem" ]; then
return 3
fi
return 2
}
To address #gammazero's comment, a longer version that (I think) is compatible with semantic versioning is this:
# Compares two dot-delimited decimal-element version numbers a and b that may
# also have arbitrary string suffixes. Compatible with semantic versioning, but
# not as strict: comparisons of non-semver strings may have unexpected
# behavior.
#
# Returns:
# 1 if a<b
# 2 if equal
# 3 if a>b
compare_versions() {
local LC_ALL=C
# Optimization
if [[ $1 == "$2" ]]; then
return 2
fi
# Compare numeric release versions. Supports an arbitrary number of numeric
# elements (i.e., not just X.Y.Z) in which unspecified indices are regarded
# as 0.
local aver=${1%%[^0-9.]*} bver=${2%%[^0-9.]*}
local arem=${1#$aver} brem=${2#$bver}
local IFS=.
local i a=($aver) b=($bver)
for ((i=0; i<${#a[#]} || i<${#b[#]}; i++)); do
if ((10#${a[i]:-0} < 10#${b[i]:-0})); then
return 1
elif ((10#${a[i]:-0} > 10#${b[i]:-0})); then
return 3
fi
done
# Remove build metadata before remaining comparison
arem=${arem%%+*}
brem=${brem%%+*}
# Prelease (w/remainder) always older than release (no remainder)
if [ -n "$arem" -a -z "$brem" ]; then
return 1
elif [ -z "$arem" -a -n "$brem" ]; then
return 3
fi
# Otherwise, split by periods and compare individual elements either
# numerically or lexicographically
local a=(${arem#-}) b=(${brem#-})
for ((i=0; i<${#a[#]} && i<${#b[#]}; i++)); do
local anns=${a[i]#${a[i]%%[^0-9]*}} bnns=${b[i]#${b[i]%%[^0-9]*}}
if [ -z "$anns$bnns" ]; then
# Both numeric
if ((10#${a[i]:-0} < 10#${b[i]:-0})); then
return 1
elif ((10#${a[i]:-0} > 10#${b[i]:-0})); then
return 3
fi
elif [ -z "$anns" ]; then
# Numeric comes before non-numeric
return 1
elif [ -z "$bnns" ]; then
# Numeric comes before non-numeric
return 3
else
# Compare lexicographically
if [[ ${a[i]} < ${b[i]} ]]; then
return 1
elif [[ ${a[i]} > ${b[i]} ]]; then
return 3
fi
fi
done
# Fewer elements is earlier
if (( ${#a[#]} < ${#b[#]} )); then
return 1
elif (( ${#a[#]} > ${#b[#]} )); then
return 3
fi
# Must be equal!
return 2
}
How about this? Seems to work?
checkVersion() {
subVer1=$1
subVer2=$2
[ "$subVer1" == "$subVer2" ] && echo "Version is same"
echo "Version 1 is $subVer1"
testVer1=$subVer1
echo "Test version 1 is $testVer1"
x=0
while [[ $testVer1 != "" ]]
do
((x++))
testVer1=`echo $subVer1|cut -d "." -f $x`
echo "testVer1 now is $testVer1"
testVer2=`echo $subVer2|cut -d "." -f $x`
echo "testVer2 now is $testVer2"
if [[ $testVer1 -gt $testVer2 ]]
then
echo "$ver1 is greater than $ver2"
break
elif [[ "$testVer2" -gt "$testVer1" ]]
then
echo "$ver2 is greater than $ver1"
break
fi
echo "This is the sub verion for first value $testVer1"
echo "This is the sub verion for second value $testVer2"
done
}
ver1=$1
ver2=$2
checkVersion "$ver1" "$ver2"
Here is another pure bash solution without any external calls:
#!/bin/bash
function version_compare {
IFS='.' read -ra ver1 <<< "$1"
IFS='.' read -ra ver2 <<< "$2"
[[ ${#ver1[#]} -gt ${#ver2[#]} ]] && till=${#ver1[#]} || till=${#ver2[#]}
for ((i=0; i<${till}; i++)); do
local num1; local num2;
[[ -z ${ver1[i]} ]] && num1=0 || num1=${ver1[i]}
[[ -z ${ver2[i]} ]] && num2=0 || num2=${ver2[i]}
if [[ $num1 -gt $num2 ]]; then
echo ">"; return 0
elif
[[ $num1 -lt $num2 ]]; then
echo "<"; return 0
fi
done
echo "="; return 0
}
echo "${1} $(version_compare "${1}" "${2}") ${2}"
And there is even more simple solution, if you are sure that the versions in question do not contain leading zeros after the first dot:
#!/bin/bash
function version_compare {
local ver1=${1//.}
local ver2=${2//.}
if [[ $ver1 -gt $ver2 ]]; then
echo ">"; return 0
elif
[[ $ver1 -lt $ver2 ]]; then
echo "<"; return 0
fi
echo "="; return 0
}
echo "${1} $(version_compare "${1}" "${2}") ${2}"
This will work for something like 1.2.3 vs 1.3.1 vs 0.9.7, but won't work with
1.2.3 vs 1.2.3.0 or 1.01.1 vs 1.1.1
I implemented yet another comparator function. This one had two specific requirements: (i) I didn't want the function to fail by using return 1 but echo instead; (ii) as we're retrieving versions from a git repository version "1.0" should be bigger than "1.0.2", meaning that "1.0" comes from trunk.
function version_compare {
IFS="." read -a v_a <<< "$1"
IFS="." read -a v_b <<< "$2"
while [[ -n "$v_a" || -n "$v_b" ]]; do
[[ -z "$v_a" || "$v_a" -gt "$v_b" ]] && echo 1 && return
[[ -z "$v_b" || "$v_b" -gt "$v_a" ]] && echo -1 && return
v_a=("${v_a[#]:1}")
v_b=("${v_b[#]:1}")
done
echo 0
}
Feel free to comment and suggest improvements.
You can use version CLI to check version's constraints
$ version ">=1.0, <2.0" "1.7"
$ go version | version ">=1.9"
Bash script example:
#!/bin/bash
if `version -b ">=9.0.0" "$(gcc --version)"`; then
echo "gcc version satisfies constraints >=9.0.0"
else
echo "gcc version doesn't satisfies constraints >=9.0.0"
fi
Wow... this is way down the list of an old question, but I think this is a pretty elegant answer. First convert each dot-separated version into its own array, using shell parameter expansion (See Shell Parameter Expansion).
v1="05.2.3" # some evil examples that work here
v2="7.001.0.0"
declare -a v1_array=(${v1//./ })
declare -a v2_array=(${v2//./ })
Now the two arrays have the version number as a numerical string in priority order. Lots of the above solutions take you from there, but it all derives from the observation that version string is just an integer with an arbitrary base. We can test finding the first unequal digit (like strcmp does for characters in a string).
compare_version() {
declare -a v1_array=(${1//./ })
declare -a v2_array=(${2//./ })
while [[ -nz $v1_array ]] || [[ -nz $v2_array ]]; do
let v1_val=${v1_array:-0} # this will remove any leading zeros
let v2_val=${v2_array:-0}
let result=$((v1_val-v2_val))
if (( result != 0 )); then
echo $result
return
fi
v1_array=("${v1_array[#]:1}") # trim off the first "digit". it doesn't help
v2_array=("${v2_array[#]:1}")
done
# if we get here, both the arrays are empty and neither has been numerically
# different, which is equivalent to the two versions being equal
echo 0
return
}
This echoes a negative number if the first version is less than the second, a zero if they're equal and a positive number if the first version is greater. Some output:
$ compare_version 1 1.2
-2
$ compare_version "05.1.3" "5.001.03.0.0.0.1"
-1
$ compare_version "05.1.3" "5.001.03.0.0.0"
0
$ compare_version "05.1.3" "5.001.03.0"
0
$ compare_version "05.1.3" "5.001.30.0"
-27
$ compare_version "05.2.3" "7.001.0.0"
-2
$ compare_version "05.1.3" "5.001.30.0"
-27
$ compare_version "7.001.0.0" "05.1.3"
2
Degenerate cases like, ".2" or "3.0." don't work (undefined results), and if non-numeric characters are present beside the '.' it might fail (haven't tested) but will certainly be undefined. So this should be paired with a sanitizing function or appropriate check for valid formatting. Also, I'm sure with some tweaking, this could be made more robust without too much extra baggage.
ver_cmp()
{
local IFS=.
local V1=($1) V2=($2) I
for ((I=0 ; I<${#V1[*]} || I<${#V2[*]} ; I++)) ; do
[[ ${V1[$I]:-0} -lt ${V2[$I]:-0} ]] && echo -1 && return
[[ ${V1[$I]:-0} -gt ${V2[$I]:-0} ]] && echo 1 && return
done
echo 0
}
ver_eq()
{
[[ $(ver_cmp "$1" "$2") -eq 0 ]]
}
ver_lt()
{
[[ $(ver_cmp "$1" "$2") -eq -1 ]]
}
ver_gt()
{
[[ $(ver_cmp "$1" "$2") -eq 1 ]]
}
ver_le()
{
[[ ! $(ver_cmp "$1" "$2") -eq 1 ]]
}
ver_ge()
{
[[ ! $(ver_cmp "$1" "$2") -eq -1 ]]
}
To test:
( ( while read V1 V2 ; do echo $V1 $(ver_cmp $V1 $V2) $V2 ; done ) <<EOF
1.2.3 2.2.3
2.2.3 2.2.2
3.10 3.2
2.2 2.2.1
3.1 3.1.0
EOF
) | sed 's/ -1 / < / ; s/ 0 / = / ; s/ 1 / > /' | column -t
1.2.3 < 2.2.3
2.2.3 > 2.2.2
3.10 > 3.2
2.2 < 2.2.1
3.1 = 3.1.0
ver_lt 10.1.2 10.1.20 && echo 'Your version is too old'
Your version is too old
When the Bash gets too complicated, just pipe it into python!
vercomp(){ echo "$1" "$2" | python3 -c "import re, sys; arr = lambda x: list(map(int, re.split('[^0-9]+', x))); x, y = map(arr, sys.stdin.read().split()); exit(not x >= y)"; }
Example comparing two version numbers:
vercomp 2.8 2.4.5 && echo ">=" || echo "<"
This python one liner compares the left version number to the right version number and exits 0 if the left version is equal or higher. It also handles versions like 2.4.5rc3
Broken down, this is the readable code:
import re, sys
# Convert a version string into a list "2.4.5" -> [2, 4, 5]
arr = lambda x: list(map(int, re.split('[^0-9]+', x)))
# Read the version numbers from stdin and apply the above function to them
x, y = map(arr, sys.stdin.read().split())
# Exit 0 if the left number is greater than the right
exit(not x >= y)
I came across and solved this problem, to add an additional (and shorter and simpler) answer...
First note, extended shell comparison failed as you may already know...
if [[ 1.2.0 < 1.12.12 ]]; then echo true; else echo false; fi
false
Using the sort -t'.'-g (or sort -V as mentioned by kanaka) to order versions and simple bash string comparison I found a solution. The input file contains versions in columns 3 and 4 which I want to compare. This iterates through the list identifying a match or if one is greater than the other. Hope this may still help anyone looking to do this using bash as simple as possible.
while read l
do
#Field 3 contains version on left to compare (change -f3 to required column).
kf=$(echo $l | cut -d ' ' -f3)
#Field 4 contains version on right to compare (change -f4 to required column).
mp=$(echo $l | cut -d ' ' -f4)
echo 'kf = '$kf
echo 'mp = '$mp
#To compare versions m.m.m the two can be listed and sorted with a . separator and the greater version found.
gv=$(echo -e $kf'\n'$mp | sort -t'.' -g | tail -n 1)
if [ $kf = $mp ]; then
echo 'Match Found: '$l
elif [ $kf = $gv ]; then
echo 'Karaf feature file version is greater '$l
elif [ $mp = $gv ]; then
echo 'Maven pom file version is greater '$l
else
echo 'Comparison error '$l
fi
done < features_and_pom_versions.tmp.txt
Thanks to Barry's blog for the sort idea...
ref: http://bkhome.org/blog/?viewDetailed=02199
### the answer is does we second argument is higher
function _ver_higher {
ver=`echo -ne "$1\n$2" |sort -Vr |head -n1`
if [ "$2" == "$1" ]; then
return 1
elif [ "$2" == "$ver" ]; then
return 0
else
return 1
fi
}
if _ver_higher $1 $2; then
echo higher
else
echo same or less
fi
It's pretty simple and small.
Thanks to Dennis's solution, we can extend it to allow comparison operators '>', '<', '=', '==', '<=', and '>='.
# compver ver1 '=|==|>|<|>=|<=' ver2
compver() {
local op
vercomp $1 $3
case $? in
0) op='=';;
1) op='>';;
2) op='<';;
esac
[[ $2 == *$op* ]] && return 0 || return 1
}
We can then use comparison operators in the expressions like:
compver 1.7 '<=' 1.8
compver 1.7 '==' 1.7
compver 1.7 '=' 1.7
and test only the true/false of the result, like:
if compver $ver1 '>' $ver2; then
echo "Newer"
fi
Here's another pure bash version, rather smaller than the accepted answer. It only checks whether a version is less than or equal to a "minimum version", and it will check alphanumeric sequences lexicographically, which often gives the wrong result ("snapshot" is not later than "release", to give a common example). It will work fine for major/minor.
is_number() {
case "$BASH_VERSION" in
3.1.*)
PATTERN='\^\[0-9\]+\$'
;;
*)
PATTERN='^[0-9]+$'
;;
esac
[[ "$1" =~ $PATTERN ]]
}
min_version() {
if [[ $# != 2 ]]
then
echo "Usage: min_version current minimum"
return
fi
A="${1%%.*}"
B="${2%%.*}"
if [[ "$A" != "$1" && "$B" != "$2" && "$A" == "$B" ]]
then
min_version "${1#*.}" "${2#*.}"
else
if is_number "$A" && is_number "$B"
then
[[ "$A" -ge "$B" ]]
else
[[ ! "$A" < "$B" ]]
fi
fi
}

Finding the greatest common divisor of two numbers in Bash

I am coding a program that computes the GCD of two numbers. My problem happens in some input cases:
GCD (88, 100) = 4
But my program returns an empty space (like it couldn't get the $gcd), but I haven't really got to the exact problem in my code yet.
#!/bin/bash
while true; do
read a b
gcd=$a
if [ $b -lt $gcd ]; then
gcd=$b
fi
while [ $gcd -ne 0 ]; do
x=`expr $a % $gcd`
y=`expr $b % $gcd`
if [ $x -eq 0 -a $y -eq 0 ]; then
echo "GCD ($a, $b) = $gcd"
break
fi
done
done
You could define a function that implements the Euclidean algorithm:
gcd() (
! (( $1 % $2 )) && echo $2 || gcd $2 $(( $1 % $2 ))
)
the function uses the ternary operator test && cmd1 || cmd2 and recursion (it calls itself). Or define a more readable version of the function:
gcd() (
if (( $1 % $2 == 0)); then
echo $2
else
gcd $2 $(( $1 % $2 ))
fi
)
Test:
$ gcd 88 100
4

AWK int comparison statements not working

I am using Hacker Rank challenges to teach myself BASH, and I'm in need of some advice.
I'm specifically trying to solve this challenge: Apple and Oranges by nabila_ahmed
I need to read in multiple lines of ints separated by spaces, on multiple lines. I decided to use awk to do this because it seems a lot more efficient in memory storage than using read. (I tried a couple of solutions using read and they timed out, because the test cases are really big.)
Example input:
7 11
5 15
3 2
-2 2 1
5 -6
This is my first attempt in bash and it timed out:
row=0
while read line || [[ -n $line ]]; do
if [ "$row" -eq 0 ]
then
column=0
for n in $line; do
if [ "$column" -eq 0 ]
then
housePos1=$n
elif [ "$column" -eq 1 ]
then
housePos2=$n
fi
((column++))
done
# Calculate house min and max
if [ "$housePos1" -gt "$housePos2" ]
then
minHousePos=$housePos2
maxHousePos=$housePos1
else
minHousePos=$housePos1
maxHousePos=$housePos2
fi
elif [ "$row" -eq 1 ]
then
column=0
for n in $line; do
if [ "$column" -eq 0 ]
then
appleTreePos=$n
elif [ "$column" -eq 1 ]
then
orangeTreePos=$n
fi
((column++))
done
elif [ "$row" -eq 3 ]
then
applesInHouse=0
for n in $line; do
# Calculate the apple's position
let applePos=$((appleTreePos + n))
# If the apple's position is within the houses position, count it
if [ "$applePos" -ge "$minHousePos" ] && [ "$applePos" -le "$maxHousePos" ]
then
((applesInHouse++))
fi
done
elif [ "$row" -eq 4 ]
then
orangesInHouse=0
for n in $line; do
# Calculate the apple's position
let orangePos=$((orangeTreePos + n))
# If the apple's position is within the houses position, count it
if [ "$orangePos" -ge "$minHousePos" ] && [ "$orangePos" -le "$maxHousePos" ]
then
((orangesInHouse++))
fi
done
fi
((row++))
done
echo "$applesInHouse"
echo "$orangesInHouse"
Here is my second attempt in bash, even more of the solutions timed out:
x=0;y=0;read -r s t;read -r a b;read -r m n;
for i in `seq 1 $m`; do
if [ "$i" -lt "$m" ]
then
read -d\ z
else
read -r z
fi
if [ "$((a+z))" -ge "$s" ] && \
[ "$((a+z))" -le "$t" ]
then
((x++))
fi
done
for i in `seq 1 $n`; do
if [ "$i" -lt "$n" ]
then
read -d\ z
else
read -r z
fi
if [ "$((b+z))" -ge "$s" ] && \
[ "$((b+z))" -le "$t" ]
then
((y++))
fi
done
echo $x; echo $y
Here's where I am at in debugging my solution using awk...
awk -v RS='[-]?[0-9]+' \
'{
if(word==$1) {
counter++
if(counter==1){
s=RT
}else if(counter==2){
t=RT
}else if(counter==3){
a=RT
}else if(counter==4){
b=RT
}else if(counter==5){
m=RT
}else if(counter==6){
n=RT
}else{
counter2++
if(counter2<=m){
print "Apples:"
print a+RT
print a+RT>=s
print a+RT<=t
applecount++
}
if(counter2>m && counter2<=m+n){
print "Oranges:"
print b+RT
print b+RT>=s
print b+RT<=t
orangecount++
}
}
}else{
counter=1
word=$1
}
}
END {
print "Total Counts:"
print applecount
print orangecount
}
'
Here is the output from that script when using the sample input
Apples:
3
0
0
Apples:
7
1
0 <-- This is the problem! (7 is less than or equal to 11)
Apples:
6
0
0
Oranges:
20
0
0
Oranges:
9
1
0 <-- This is also a problem! (9 is less than or equal to 11)
Total Counts:
3
2
As you can see, I'm getting some of the wrong comparisons...
ANSWER
(mostly courtesy of #glenn-jackman)
apples_oranges() {
local s t a b m n d
local -a apples oranges
local na=0 nb=0
{
read s t
read a b
read m n
read -a apples
read -a oranges
} < "$1"
for d in "${apples[#]}"; do
(( s <= a+d && a+d <= t )) && ((na++))
done
echo $na
for d in "${oranges[#]}"; do
(( s <= b+d && b+d <= t )) && ((nb++))
done
echo $nb
}
apples_oranges /dev/stdin
I'd do this with bash
apples_oranges() {
local s t a b m n d
local -a apples oranges
local na=0 nb=0
{
read s t
read a b
read m n # unused
read -a apples
read -a oranges
} < "$1"
for d in "${apples[#]}"; do
(( a+d >= s )) && ((na++))
done
echo $na
for d in "${oranges[#]}"; do
(( b-d <= t )) && ((nb++))
done
echo $nb
}
apples_oranges input.txt
this may get you started...
$ awk '
NR==1{split($0,house)}
NR==2{split($0,trees)}
NR==3{split($0,counts)}
NR==4{split($0,apples)}
NR==5{split($0,oranges)}
END{for(i in apples)
if(trees[1]+apples[i]>=house[1] && trees[1]+apples[i]<=house[2]) a++; print a}' file

how can i mix or and and in an if statement in bash?

i have this function that accepts 3 parameters , ech one contain of 4 numbers and a capital letter for example : "1234A"
and i want to print 1 if the second parameter is bigger than the third one and smaller than the first one ,
i wrote this function that i cutted the 4 numbers in a parameter for each parameter and the letter in diffrent paramater for each one and i began to compare
but the problem it print nothing !!
anyone know how to do things in one if statement rather than two if statements ??
what i did :
function check {
curr_letter=`echo "$1" | cut -c5`
min_letter=`echo "$3" | cut -c5`
sm_letter=`echo "$2" | cut -c5`
curr_nums=`echo "$1" | cut -c1-4`
min_nums=`echo "$3" | cut -c1-4`
sm_nums=`echo "$2" | cut -c1-4`
if [[ sm_nums -eq curr_nums && sm_letter < curr_letter ]] ; then
if [[ sm_nums -eq min_nums && sm_letter > min_letter ]] ; then
echo 1
fi
if [[ sm_nums > min_nums ]] ; then
echo 1
fi
fi
if [[ sm_nums < curr_nums ]] ; then
if [[ sm_nums -eq min_nums && sm_letter > min_letter ]] ; then
echo 1
fi
if [[ sm_nums > min_nums ]] ; then
echo 1
fi
fi
}
i get nothing when i test this in bash , i get an empty line..
this is how i tested it :
p=`check "1617B" "1617A" "0000A"` echo $p
You can omit the $ in variable names within arithmetic context ((...)).
Within [[ ... ]] you cannot omit it.
Instead of calling echo ... | cut -c..., you can easily extract substrings using Bash's very own syntax {var:start:length}.
Within a [[ ... ]] or ((...)),
use == instead of -eq.
Note however that < and > operators sort lexicographically within a [[ ... ]], but numerically in arithmetic context ((...)).
Therefore the string-valued variables (named *_letter in your example)
should be compared within [[ ... ]], the numeric variables (named *_nums in your example) should be compared within ((...)).
Like this:
function check() {
curr_letter=${1:4:1}
min_letter=${3:4:1}
sm_letter=${2:4:1}
curr_nums=${1:0:4}
min_nums=${3:0:4}
sm_nums=${2:0:4}
if (( sm_nums == curr_nums )) && [[ $sm_letter < $curr_letter ]]; then
if (( sm_nums == min_nums )) && [[ $sm_letter > $min_letter ]] ; then
echo 1
fi
if (( sm_nums > min_nums )) ; then
echo 1
fi
fi
if (( sm_nums < curr_nums )) ; then
if (( sm_nums == min_nums )) && [[ $sm_letter > $min_letter ]] ; then
echo 1
fi
if (( sm_nums > min_nums )) ; then
echo 1
fi
fi
}
Lastly, instead of p=`check "1617B" "1617A" "0000A"`; echo $p,
better write like this:
echo $(check "1617B" "1617A" "0000A")
why not just
awk '$3 <= $2 && $2 <= $1 {print 1}'
or if you need a function
check() { awk '$3 <= $2 && $2 <= $1 {print 1}' <<< "$#"; }
or
check() { awk "BEGIN{if($3 <= $2 && $2 <= $1) print 1}"; }

Bash always printing same value regardless of boolean value

Related to SO.
fizzy.sh:
#!/usr/bin/env sh
div3() {
expr $1 % 3 = 0
}
div5() {
expr $1 % 5 = 0
}
fizzy() {
if [ $(div3 $1) ] && [ $(div5 $1) ]; then
expr "FizzBuzz"
elif [ $(div3 $1) ]; then
expr "Fizz"
elif [ $(div5 $1) ]; then
expr "Buzz"
else
expr "$1"
fi
}
echo $(fizzy 1)
echo $(fizzy 2)
echo $(fizzy 3)
Example:
$ ./fizzy.sh
FizzBuzz
FizzBuzz
FizzBuzz
expr $1 % 3 = 0 yields 1 or 0, depending on whether the result of $1 % 3 is zero or not, but if treats 0 as true, not false.
sh-3.2$ if [ 0 ]; then echo ok; fi
ok
So you'd need to compare the output of your function against 1. Something like this:
#!/usr/bin/env sh
div3() {
expr $1 % 3 = 0
}
div5() {
expr $1 % 5 = 0
}
fizzy() {
if [ $(div3 $1) -eq 1 ] && [ $(div5 $1) -eq 1 ]; then
expr "FizzBuzz"
elif [ $(div3 $1) -eq 1 ]; then
expr "Fizz"
elif [ $(div5 $1) -eq 1 ]; then
expr "Buzz"
else
expr "$1"
fi
}
for (( i = 1; i <= 15; i++ ))
do
echo $(fizzy $i)
done
Without the need for div3 or div5 functions.
fizzbuzz() { # eg: fizzbuzz 10
((($1%15==0))&& echo FizzBuzz)||
((($1%5==0))&& echo Buzz)||
((($1%3==0))&& echo Fizz)||
echo $1;
}
Or you could do it all at once
fizzbuzz() { # eg: fizzbuzz
for i in {1..100};
do
((($i%15==0))&& echo FizzBuzz)||
((($i%5==0))&& echo Buzz)||
((($i%3==0))&& echo Fizz)||
echo $i;
done;
}
If your shell is bash, you don't need to call out to expr:
div3() { (( $1 % 3 == 0 )); }
div5() { (( $1 % 5 == 0 )); }
fizzbuzz() {
if div3 $1 && div5 $1; then
echo FizzBuzz
elif div3 $1; then
echo Fizz
elif div5 $1; then
echo Buzz
else
echo
fi
}
for ((n=10; n<=15; n++)); do
printf "%d\t%s\n" $n $(fizzbuzz $n)
done

Resources