Finding the greatest common divisor of two numbers in Bash - 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

Related

Print Fibonacci series using recursion in bash with only 1 variable

I'd like to know how to print Fibonacci series using recursion in bash with only 1 variable.
From what I've done:
fib()
{
i=$1
if (( $i <= 1 ))
then echo 0
elif (( $i == 2 ))
then echo 1
else
echo $(( $(fib $(($i - 1)) ) + $(fib $(($i - 2)) ) ))
fi
}
echo $(fib $1)
I get the correct output of the final iteration, for example if I enter 10 I will get 34, but I'd like to print the whole sequence of numbers, i.e. all the iterations as well. How can I achieve that?
Another way I tried was by:
#!/bin/bash
arr[0]=0
arr[1]=1
for (( i=0; i<=10; i++ ))
do
echo -n "${arr[0]} "
arr[0]=$((${arr[0]} + ${arr[1]} ))
arr[1]=$((${arr[0]} - ${arr[1]} ))
done
echo ""
But obviously here I've used a for loop, but I don't want to use another variable.
Just for (my kind of) fun, this code prints the Fibonacci numbers from the 0th to the 92nd (as defined in Fibonacci number - Wikipedia) with a recursive function that uses no variables:
#! /bin/bash
function fib
{
echo ${3-0}
(($1 > 0)) && fib $(($1-1)) ${3-0} $((${2-1}+${3-0}))
}
fib 92
Some may claim that using the positional parameters ($1, $2, $3) for this is cheating, but then other solutions could be said to be using two variables ($i and $1).
The code takes under 0.01 seconds to run on my (oldish) Linux machine.
The code should work with numbers up to 92 with Bash version 3 or later on any platform. See Bash Number Limit?. Numbers higher than 93 will cause to code to produce garbage results due to arithmetic overflow.
Variables in bash are global by default. You need to make i local explicitly.
fib () {
local i
i=$1
if (( i <= 1 )); then
echo $i
else
echo $(( $(fib $((i-1)) ) + $(fib $((i - 2)) ) ))
fi
}
(Also, your base cases are a little off if you are starting with 0, and 2 need not be a base case; fib 2 can be derived from the base cases fib 0 and fib 1.)
If you want to print each fibonacci value from 1 to $n, I suggest:
fib_r() {
local i=$1
if (( i < 0 )); then
echo "Error: negative numbers not allowed" >&2
exit 1
elif (( i <= 1 )); then
echo $i
else
echo $(( $($FUNCNAME $((i - 1)) ) + $($FUNCNAME $((i - 2)) ) ))
fi
}
fib() {
local i
for (( i = 1; i <= $1; i++ )); do
fib_r $i
done
}
fib 10
outputs
0
1
1
2
3
5
8
13
21
34
It's still one variable, albeit one per function.
I use the bash variable $FUNCNAME in the recursive function so you don't have to hardcode the function name within itself. I got bit by not updating that line when I renamed the function.
Of course your performance will greatly improve if you cache the results: "fib 16" takes, on my VM, about 3.5 sec without caching and about 0.03 sec with caching.
fib_r() {
local i=$1
if (( i < 0 )); then
echo "Error: negative numbers not allowed" >&2
exit 1
elif [[ -n ${fib_cache[i]} ]]; then
echo "${fib_cache[i]}"
elif (( i <= 1 )); then
echo $i
else
echo $(( $( $FUNCNAME $((i - 1)) ) + $( $FUNCNAME $((i - 2)) ) ))
fi
}
fib_cache=()
fib() {
local i
for ((i=1; i<=$1; i++)); do
fib_cache[i]=$(fib_r $i)
echo "${fib_cache[i]}"
done
}

Quicksort implemented in shell script doesn't work

I am learning shell scripting and trying to implement quick sort using it.
But it doesn't work, actually it acting weird.
The script:
#!/bin/bash
declare -a data=()
declare -r size=10
declare -i steps=0
for i in $(seq 0 $size); do
data[$i]=$(expr $RANDOM % $size)
done
function partition() {
pivot=${data[$1]}
left=$(expr $1 + 1)
right=$2
while true; do
while [[ $left -le $right && ${data[$left]} -le $pivot ]]; do
left=$(expr $left + 1)
steps=$(expr $steps + 1)
done
while [[ $right -ge $left && ${data[$right]} -ge $pivot ]]; do
right=$(expr $right - 1)
steps=$(expr $steps + 1)
done
if [[ $left -gt $right ]]; then
break
fi
temp=${data[$left]}
data[$left]=${data[$right]}
data[$right]=$temp
done
temp=${data[$1]}
data[$1]=${data[$right]}
data[$right]=$temp
echo $right
}
function quickSort() {
if [[ $1 -lt $2 ]]; then
local partitionPoint=$(partition $1 $2)
quickSort $1 $(expr $partitionPoint - 1)
quickSort $(expr $partitionPoint + 1) $2
fi
}
# involve the algorithm
quickSort 0 $(expr $size - 1)
echo "Steps: $steps"
echo ${data[#]}
I tried to log some variable but it's just weird I can't figure out what's going on.
When I comment out all the code in the two functions and 'manually' update elements of data variable, it did changed.
I tried to log some variables and they all changing.
But the final output remains untouched.
Or maybe it eventually reversed all the flipping but I don't know.
I can't figure it out.
At last I compare my python implementation line by line. No mistakes. But it just not working.
Am I miss something?
Variable scope or something?
Any advice will be appreciated.
There are several smaller issues in this code, but the biggest issue is here:
partitionPoint=$(partition $1 $2)
This is problematic because $( ... ) runs ... in a subshell -- a separate, fork()ed-off process, consequently with its own variable scope.
If you instead return your result via indirect assignment, making it:
partition "$1" "$2" partitionPoint
and inside the function using:
printf -v "$3" %s "$right"
...to assign the value to the variable so named, things work much better.
#!/bin/bash
PS4=':$LINENO+'; set -x
data=()
size=10
steps=0
for ((i=0; i<size; i++)); do
data[$i]=$((RANDOM % size))
done
partition() {
local pivot left right dest temp
pivot=${data[$1]}
left=$(($1 + 1))
right=$2
dest=$3
while true; do
while (( left <= right )) && (( ${data[$left]} <= pivot )); do
left=$(( left + 1 ))
steps=$(( steps + 1 ))
done
while (( right >= left )) && (( ${data[$right]} >= pivot )); do
right=$(( right - 1 ))
steps=$(( steps + 1 ))
done
(( left > right )) && break
temp=${data[$left]}
data[$left]=${data[$right]}
data[$right]=$temp
done
: '$1='"$1" right="$right" 'data[$1]='"${data[$1]}" 'data[$right]='"${data[$right]}"
temp=${data[$1]}
data[$1]=${data[$right]}
data[$right]=$temp
printf -v "$dest" %s "$right"
}
quickSort() {
local partitionPoint
if (( $1 < $2 )); then
partition "$1" "$2" partitionPoint
quickSort "$1" "$(( partitionPoint - 1 ))"
quickSort "$((partitionPoint + 1))" "$2"
fi
}
# involve the algorithm
quickSort 0 "$(( size - 1 ))"
echo "Steps: $steps"
printf '%s\n' "${data[#]}"

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}"; }

Comparisons numbers syntax in 'if' (bash) [duplicate]

I'm unable to get numeric comparisons working:
echo "enter two numbers";
read a b;
echo "a=$a";
echo "b=$b";
if [ $a \> $b ];
then
echo "a is greater than b";
else
echo "b is greater than a";
fi;
The problem is that it compares the number from the first digit on, i.e., 9 is bigger than 10, but 1 is greater than 09.
How can I convert the numbers into a type to do a true comparison?
In Bash, you should do your check in an arithmetic context:
if (( a > b )); then
...
fi
For POSIX shells that don't support (()), you can use -lt and -gt.
if [ "$a" -gt "$b" ]; then
...
fi
You can get a full list of comparison operators with help test or man test.
Like this:
#!/bin/bash
a=2462620
b=2462620
if [ "$a" -eq "$b" ]; then
echo "They're equal";
fi
Integers can be compared with these operators:
-eq # Equal
-ne # Not equal
-lt # Less than
-le # Less than or equal
-gt # Greater than
-ge # Greater than or equal
See this cheatsheet.
There is also one nice thing some people might not know about:
echo $(( a < b ? a : b ))
This code will print the smallest number out of a and b
In Bash I prefer doing this as it addresses itself more as a conditional operation unlike using (( )) which is more of arithmetic.
[[ n -gt m ]]
Unless I do complex stuff like
(( (n + 1) > m ))
But everyone just has their own preferences. Sad thing is that some people impose their unofficial standards.
You can also do this:
[[ 'n + 1' -gt m ]]
Which allows you to add something else which you could do with [[ ]] besides arithmetic stuff.
The bracket stuff (e.g., [[ $a -gt $b ]] or (( $a > $b )) ) isn't enough if you want to use float numbers as well; it would report a syntax error. If you want to compare float numbers or float number to integer, you can use (( $(bc <<< "...") )).
For example,
a=2.00
b=1
if (( $(bc <<<"$a > $b") )); then
echo "a is greater than b"
else
echo "a is not greater than b"
fi
You can include more than one comparison in the if statement. For example,
a=2.
b=1
c=1.0000
if (( $(bc <<<"$b == $c && $b < $a") )); then
echo "b is equal to c but less than a"
else
echo "b is either not equal to c and/or not less than a"
fi
That's helpful if you want to check if a numeric variable (integer or not) is within a numeric range.
One-line solution.
a=2
b=1
[[ ${a} -gt ${b} ]] && echo "true" || echo "false"
gt reference: https://www.gnu.org/software/bash/manual/html_node/Bash-Conditional-Expressions.html
&& reference: https://www.gnu.org/software/bash/manual/html_node/Shell-Arithmetic.html
[[...]] construct reference: https://www.gnu.org/software/bash/manual/bash.html#index-_005b_005b
${} reference: https://pubs.opengroup.org/onlinepubs/9699919799/utilities/V3_chap02.html#tag_18_06_02 (2.6.2)
The format for parameter expansion is as follows:
${expression}
where expression consists of all characters until the matching '}'.
Any '}' escaped by a or within a quoted string, and
characters in embedded arithmetic expansions, command substitutions,
and variable expansions, shall not be examined in determining the
matching '}'.
The simplest form for parameter expansion is:
${parameter}
This code can also compare floats. It is using AWK (it is not pure Bash). However, this shouldn't be a problem, as AWK is a standard POSIX command that is most likely shipped by default with your operating system.
$ awk 'BEGIN {return_code=(-1.2345 == -1.2345) ? 0 : 1; exit} END {exit return_code}'
$ echo $?
0
$ awk 'BEGIN {return_code=(-1.2345 >= -1.2345) ? 0 : 1; exit} END {exit return_code}'
$ echo $?
0
$ awk 'BEGIN {return_code=(-1.2345 < -1.2345) ? 0 : 1; exit} END {exit return_code}'
$ echo $?
1
$ awk 'BEGIN {return_code=(-1.2345 < 2) ? 0 : 1; exit} END {exit return_code}'
$ echo $?
0
$ awk 'BEGIN {return_code=(-1.2345 > 2) ? 0 : 1; exit} END {exit return_code}'
$ echo $?
To make it shorter for use, use this function:
compare_nums()
{
# Function to compare two numbers (float or integers) by using AWK.
# The function will not print anything, but it will return 0 (if the comparison is true) or 1
# (if the comparison is false) exit codes, so it can be used directly in shell one liners.
#############
### Usage ###
### Note that you have to enclose the comparison operator in quotes.
#############
# compare_nums 1 ">" 2 # returns false
# compare_nums 1.23 "<=" 2 # returns true
# compare_nums -1.238 "<=" -2 # returns false
#############################################
num1=$1
op=$2
num2=$3
E_BADARGS=65
# Make sure that the provided numbers are actually numbers.
if ! [[ $num1 =~ ^-?[0-9]+([.][0-9]+)?$ ]]; then >&2 echo "$num1 is not a number"; return $E_BADARGS; fi
if ! [[ $num2 =~ ^-?[0-9]+([.][0-9]+)?$ ]]; then >&2 echo "$num2 is not a number"; return $E_BADARGS; fi
# If you want to print the exit code as well (instead of only returning it), uncomment
# the awk line below and comment the uncommented one which is two lines below.
#awk 'BEGIN {print return_code=('$num1' '$op' '$num2') ? 0 : 1; exit} END {exit return_code}'
awk 'BEGIN {return_code=('$num1' '$op' '$num2') ? 0 : 1; exit} END {exit return_code}'
return_code=$?
return $return_code
}
$ compare_nums -1.2345 ">=" -1.2345 && echo true || echo false
true
$ compare_nums -1.2345 ">=" 23 && echo true || echo false
false
If you have floats, you can write a function and then use that. For example,
#!/bin/bash
function float_gt() {
perl -e "{if($1>$2){print 1} else {print 0}}"
}
x=3.14
y=5.20
if [ $(float_gt $x $y) == 1 ] ; then
echo "do stuff with x"
else
echo "do stuff with y"
fi
I solved this by using a small function to convert version strings to plain integer values that can be compared:
function versionToInt() {
local IFS=.
parts=($1)
let val=1000000*parts[0]+1000*parts[1]+parts[2]
echo $val
}
This makes two important assumptions:
The input is a "normal SemVer string"
Each part is between 0-999
For example
versionToInt 12.34.56 # --> 12034056
versionToInt 1.2.3 # --> 1002003
Example testing whether npm command meets the minimum requirement...
NPM_ACTUAL=$(versionToInt $(npm --version)) # Capture npm version
NPM_REQUIRED=$(versionToInt 4.3.0) # Desired version
if [ $NPM_ACTUAL \< $NPM_REQUIRED ]; then
echo "Please update to npm#latest"
exit 1
fi
Just adding to all the above answers:
If you have more than one expression in single if statement, you can do something like this:
if (( $a % 2 == 0 )) && (( $b % 2 != 0));
then
echo "What you want to do"
fi
Hope this helps!

How can I compare numbers in Bash?

I'm unable to get numeric comparisons working:
echo "enter two numbers";
read a b;
echo "a=$a";
echo "b=$b";
if [ $a \> $b ];
then
echo "a is greater than b";
else
echo "b is greater than a";
fi;
The problem is that it compares the number from the first digit on, i.e., 9 is bigger than 10, but 1 is greater than 09.
How can I convert the numbers into a type to do a true comparison?
In Bash, you should do your check in an arithmetic context:
if (( a > b )); then
...
fi
For POSIX shells that don't support (()), you can use -lt and -gt.
if [ "$a" -gt "$b" ]; then
...
fi
You can get a full list of comparison operators with help test or man test.
Like this:
#!/bin/bash
a=2462620
b=2462620
if [ "$a" -eq "$b" ]; then
echo "They're equal";
fi
Integers can be compared with these operators:
-eq # Equal
-ne # Not equal
-lt # Less than
-le # Less than or equal
-gt # Greater than
-ge # Greater than or equal
See this cheatsheet.
There is also one nice thing some people might not know about:
echo $(( a < b ? a : b ))
This code will print the smallest number out of a and b
In Bash I prefer doing this as it addresses itself more as a conditional operation unlike using (( )) which is more of arithmetic.
[[ n -gt m ]]
Unless I do complex stuff like
(( (n + 1) > m ))
But everyone just has their own preferences. Sad thing is that some people impose their unofficial standards.
You can also do this:
[[ 'n + 1' -gt m ]]
Which allows you to add something else which you could do with [[ ]] besides arithmetic stuff.
The bracket stuff (e.g., [[ $a -gt $b ]] or (( $a > $b )) ) isn't enough if you want to use float numbers as well; it would report a syntax error. If you want to compare float numbers or float number to integer, you can use (( $(bc <<< "...") )).
For example,
a=2.00
b=1
if (( $(bc <<<"$a > $b") )); then
echo "a is greater than b"
else
echo "a is not greater than b"
fi
You can include more than one comparison in the if statement. For example,
a=2.
b=1
c=1.0000
if (( $(bc <<<"$b == $c && $b < $a") )); then
echo "b is equal to c but less than a"
else
echo "b is either not equal to c and/or not less than a"
fi
That's helpful if you want to check if a numeric variable (integer or not) is within a numeric range.
One-line solution.
a=2
b=1
[[ ${a} -gt ${b} ]] && echo "true" || echo "false"
gt reference: https://www.gnu.org/software/bash/manual/html_node/Bash-Conditional-Expressions.html
&& reference: https://www.gnu.org/software/bash/manual/html_node/Shell-Arithmetic.html
[[...]] construct reference: https://www.gnu.org/software/bash/manual/bash.html#index-_005b_005b
${} reference: https://pubs.opengroup.org/onlinepubs/9699919799/utilities/V3_chap02.html#tag_18_06_02 (2.6.2)
The format for parameter expansion is as follows:
${expression}
where expression consists of all characters until the matching '}'.
Any '}' escaped by a or within a quoted string, and
characters in embedded arithmetic expansions, command substitutions,
and variable expansions, shall not be examined in determining the
matching '}'.
The simplest form for parameter expansion is:
${parameter}
This code can also compare floats. It is using AWK (it is not pure Bash). However, this shouldn't be a problem, as AWK is a standard POSIX command that is most likely shipped by default with your operating system.
$ awk 'BEGIN {return_code=(-1.2345 == -1.2345) ? 0 : 1; exit} END {exit return_code}'
$ echo $?
0
$ awk 'BEGIN {return_code=(-1.2345 >= -1.2345) ? 0 : 1; exit} END {exit return_code}'
$ echo $?
0
$ awk 'BEGIN {return_code=(-1.2345 < -1.2345) ? 0 : 1; exit} END {exit return_code}'
$ echo $?
1
$ awk 'BEGIN {return_code=(-1.2345 < 2) ? 0 : 1; exit} END {exit return_code}'
$ echo $?
0
$ awk 'BEGIN {return_code=(-1.2345 > 2) ? 0 : 1; exit} END {exit return_code}'
$ echo $?
To make it shorter for use, use this function:
compare_nums()
{
# Function to compare two numbers (float or integers) by using AWK.
# The function will not print anything, but it will return 0 (if the comparison is true) or 1
# (if the comparison is false) exit codes, so it can be used directly in shell one liners.
#############
### Usage ###
### Note that you have to enclose the comparison operator in quotes.
#############
# compare_nums 1 ">" 2 # returns false
# compare_nums 1.23 "<=" 2 # returns true
# compare_nums -1.238 "<=" -2 # returns false
#############################################
num1=$1
op=$2
num2=$3
E_BADARGS=65
# Make sure that the provided numbers are actually numbers.
if ! [[ $num1 =~ ^-?[0-9]+([.][0-9]+)?$ ]]; then >&2 echo "$num1 is not a number"; return $E_BADARGS; fi
if ! [[ $num2 =~ ^-?[0-9]+([.][0-9]+)?$ ]]; then >&2 echo "$num2 is not a number"; return $E_BADARGS; fi
# If you want to print the exit code as well (instead of only returning it), uncomment
# the awk line below and comment the uncommented one which is two lines below.
#awk 'BEGIN {print return_code=('$num1' '$op' '$num2') ? 0 : 1; exit} END {exit return_code}'
awk 'BEGIN {return_code=('$num1' '$op' '$num2') ? 0 : 1; exit} END {exit return_code}'
return_code=$?
return $return_code
}
$ compare_nums -1.2345 ">=" -1.2345 && echo true || echo false
true
$ compare_nums -1.2345 ">=" 23 && echo true || echo false
false
If you have floats, you can write a function and then use that. For example,
#!/bin/bash
function float_gt() {
perl -e "{if($1>$2){print 1} else {print 0}}"
}
x=3.14
y=5.20
if [ $(float_gt $x $y) == 1 ] ; then
echo "do stuff with x"
else
echo "do stuff with y"
fi
I solved this by using a small function to convert version strings to plain integer values that can be compared:
function versionToInt() {
local IFS=.
parts=($1)
let val=1000000*parts[0]+1000*parts[1]+parts[2]
echo $val
}
This makes two important assumptions:
The input is a "normal SemVer string"
Each part is between 0-999
For example
versionToInt 12.34.56 # --> 12034056
versionToInt 1.2.3 # --> 1002003
Example testing whether npm command meets the minimum requirement...
NPM_ACTUAL=$(versionToInt $(npm --version)) # Capture npm version
NPM_REQUIRED=$(versionToInt 4.3.0) # Desired version
if [ $NPM_ACTUAL \< $NPM_REQUIRED ]; then
echo "Please update to npm#latest"
exit 1
fi
Just adding to all the above answers:
If you have more than one expression in single if statement, you can do something like this:
if (( $a % 2 == 0 )) && (( $b % 2 != 0));
then
echo "What you want to do"
fi
Hope this helps!

Resources