Linux Shell Scripting recursive exponentation - linux

I am writing a script that takes 2 numbers as an input and uses recursion to power on number to the power of the other, simple exponentiation. However I am new to scripting and cannot figure out where my syntax is errored here.
Here is the script
#!/bin/bash
echo "Enter number: "
read number
echo "Enter power: "
read power
echo "Powering $number to power of $power!"
exp () {
if [ $2 = 1 ]
then
return $1
fi
return $1 * $(exp $1 $2-1 )
}
result=$(exp $number, $power)
echo "Result: $result"
Currently, it kind of freezes, im not sure if I use the parameters correctly (in terms of syntax).

You need $(( )) to force arithmetic evaluation. Then you can do it with return values:
number=2 power=7
exp () {
if [ $2 -eq 1 ]; then return $1; fi
exp $1 $(($2-1))
return $(($1 * $?))
}
exp $number $power; result=$?
echo "Result: $result"
but it's a bad idea, because shells kind of reserve nonzero return values to communicate failure (e.g. the above solution
will "break" set -e).
More idiomatically, you can use stdout:
set -e
number=2 power=7
exp () {
if [ $2 -eq 1 ]; then echo $1; return; fi
echo $(($1 * $(exp $1 $(($2-1)) ) ))
}
result=$(exp $number $power)
echo "Result: $result"
but that's kind of inefficient with all the subshells.
Best to avoid the recursion and simply loop:
number=2 power=7
exp () {
local res=1 i=0;
while [ $i -lt $2 ]; do res=$((res*$1)); i=$((i+1)); done
echo $res
}
exp $number $power

Related

Nested if statements with nested loops

I am trying to make a simple calculator. I am sure if you even just glanced at this code you will see what I am trying to do.
Enter a number, then choose an operand, then vim should print out a table up to 15 with your number and operand...
Maybe this method is silly, trying to nest a load of loops in nested if statements. But I am new to bash.
The error is 'Unexpected token near else' line 24 but I feel there is a fundamental issue with the nests I do not understand.
Here is current code.
#!/bin/bash
choice=6
read -p "Enter a number bruv" num
#choose operand.
echo "Now choose an operand comrade"
#choices
echo "1. *"
echo "2. +"
echo "3. -"
echo "4. /"
echo "5. ^"
echo -n "Please choose [1,2,3,4,5]"
while [ $choice -eq 6 ]; do
read choice
if [ $choice -eq 1 ] ; then
for((i=0;i<=15;i++))
do
echo -n "$i * $num = $[ $i * $num ] "
echo " "
else
if [ $choice -eq 2 ] ; then
for((i=0;i<=15;i++))
do
echo -n "$i + $num = $[ $i + $num ] "
echo " "
else
if [ $choice -eq 3 ] ; then
for((i=0;i<=15;i++))
do
echo -n "$i - $num = $[ $i - $num ] "
echo " "
else
if [ $choice -eq 4 ] ; then
for((i=0;i<=15;i++))
do
echo -n "$i / $num = $[ $i / $num ] "
echo " "
else
if [ $choice -eq 5 ] ; then
for((i=0;i<=15;i++))
do
echo -n "$i ^$num = $[ $i ^$num ] "
echo " "
else echo "Please choose between 1 and 5!!!"
echo "1. *"
echo "2. +"
echo "3. -"
echo "4. /"
echo "5. ^"
echo -n "Please choose [1,2,3,4,5]"
fi
fi
fi
fi
fi
done
Would it be better to implement this?
# !/bin/bash
# Take user Input
echo "Enter number : "
read a
# Input type of operation
echo "Enter Choice :"
echo "1. Addition"
echo "2. Subtraction"
echo "3. Multiplication"
echo "4. Division"
echo "5. Power"
read ch
# Switch Case to perform
# calulator operations
case $ch in
1)res=`for((i=0;i<=15;i++))
do
echo -n "$i - $num = $[ $i - $num ] "
echo " "`
;;
2)res=`for((i=0;i<=15;i++))
do
echo -n "$i - $num = $[ $i - $num ] "
echo " "`
;;
3)res=`for((i=0;i<=15;i++))
do
echo -n "$i - $num = $[ $i - $num ] "
echo " "`
;;
4)res=`for((i=0;i<=15;i++))
do
echo -n "$i - $num = $[ $i - $num ] "
echo " "c`
;;
esac
echo "Result : $res" ```
Here is a solution, using a function:
#! /bin/bash
ITER_MAX=15
show_values() # n op
{
local n=$1 op=$2
for ((i=0; i<=ITER_MAX; i++)); do
((i>0)) && echo -n " ; "
echo -n "$i $op $n = $((i $op n))"
done
echo
}
# Take user Input
read -p "Enter number : " a
# Input type of operation
echo "Enter Choice (Ctrl+C to stop):"
PS3=">> "
select ch in Addition Subtraction Multiplication Division Power ; do
case "$ch" in
Add*) op="+" ;;
Sub*) op="-" ;;
Mul*) op="*" ;;
Div*) op="/" ;;
Pow*) op="**" ;;
*) echo "Bad choice, abort" >&2 ; break ;;
esac
show_values "$a" "$op"
done
Some explanations:
(( )) is arithmetic evaluation and $(( )) is arithmetic expansion
((i>0)) && echo -n " ; " is equivalent to if ((i>0)); then echo -n " ; " ; fi
read -p "Enter number : " a is equivalent to echo -n "Enter number : " ; read a
about select, see help select in your bash terminal.
Use https://www.shellcheck.net/
Line 20:
for((i=0;i<=15;i++))
^-- SC1009: The mentioned syntax error was in this for loop.
^-- SC1073: Couldn't parse this arithmetic for condition. Fix to allow more checks.
Line 21:
do
^-- SC1061: Couldn't find 'done' for this 'do'.
Line 24:
else
^-- SC1062: Expected 'done' matching previously mentioned 'do'.
^-- SC1072: Unexpected keyword/token. Fix any mentioned problems and try again.
An alternate take:
read -p "Enter a number and an operator: " -a a
for n in {1..15}; do printf "%+10s\n" $((${a[#]} $n)); done
-p tells read to supply a prompt. -a reads into an array (named a here).
{1..15} is built-in bash sequence syntax.
%+10s tells printf to space-pad/right justify out to 10 characters.
${a[#]} is replaced with all the elements of the array - the number and then operator.
$(( ... )) does arithmetic processing and replaces itself with the result (as a string).
So if you enter "10 *" then $((${a[#]} $n)) processes 10 * 1 the first time, so printf "%+10s\n" $((${a[#]} $n)) outputs " 10" with a trailing newline character. Then the loop replaces the 1 with the next number on each iteration, up to 15. This also allows other operators, such as % for modulus, but will crash if something invalid is given.
If you want error checking -
while [[ ! "${a[*]}" =~ ^[0-9][0-9]*\ ([*/+-]|\*\*)$ ]]
do read -p "Enter a, integer and an operator (one of: + - * / **) " -a a
done
for n in {1..15}; do printf "%+10s\n" $((${a[#]} $n)); done
If you want floating point math, try bc:
while [[ ! "${a[*]}" =~ ^[0-9]+.?[0-9]*\ ([*/+-]|\*\*)$ ]]
do read -p "Enter a, integer and an operator (one of: + - * / **) " -a a
done
for n in {1..15}; do printf "%+15s\n" $(bc -l <<< "scale=3;${a[#]} $n"); done
Personally, I'd put the inputs on the command line. Less futzy, though it might require quoting the * operator, depending on how you run it and what's in the directory.
#!/bin/bash
me=${0##*/}
use="
use: $me {number} {operator} [iterations] [scale]
Numbers may include one decimal point.
Operators must be one of: + - * / **
"
while getopts "n:o:i:s:" arg
do case $arg in
n) n="$OPTARG" ;;
o) o="$OPTARG" ;;
i) i="$OPTARG" ;;
s) s="$OPTARG" ;;
*) printf "%s\n" "Invalid argument '$arg' $use";
exit 1;;
esac
done
[[ -n "$n" && -n "$o" ]] || { echo "$use"; exit 1; }
[[ "$n" =~ ^[0-9]+.?[0-9]*$ ]] || { printf "%s\n" "Invalid number '$n' $use"; exit 1; }
[[ "$o" =~ ^([*/^+-]|\*\*)$ ]] || { printf "%s\n" "Invalid operator '$o' $use"; exit 1; }
[[ "${i:=15}" =~ ^[0-9]+.?[0-9.]$ ]] || { printf "%s\n" "Invalid number '$i' $use"; exit 1; }
[[ "${s:=3}" =~ ^[0-9]+$ ]] || { printf "%s\n" "Invalid scale '$s' (must be an integer) $use"; exit 1; }
c=1
while ((c < i))
do printf "%+15s\n" $(bc -l <<< "scale=$s; $n $o $c")
((c++))
done

Bash Script - return actually returns something else

My Original Code:
# Factorial Using Recursion
res=1
fact()
{
x=$1
if [ $x -le 1 ]
then
echo "Actual - $res"
return `expr $res`
else
#echo $x
res=$(($res * $x))
echo "($res)"
fact $[$x-1]
fi
}
fact $1
echo "Factorial of $1 = $?"
The result (stored in $res) is certainly what I want and is also correct. But somehow when it is being returned and caught by $? it becomes erroneous.
The maximum value of a return code is 255 so there's no way that you can return the factorial of most integers.
Instead, your function should output the value to standard output, e.g. using echo. Capture the output of the function like result=$(fact "$x").
If you want to output other messages while the function is executing then you can print to standard error using echo 'whatever' >&2.
res=1
fact()
{
x=$1
if [ "$x" -le 1 ]
then
echo "$res"
else
res=$((res * x))
fact "$((x - 1))"
fi
}
I've also fixed up your syntax a little bit.

Parameters in Linux shell script

I am trying to create a script file in Linux that acts as a basic calculator. It needs to pass 3 parameters or no parameters.
If it has 3 parameters, it should be able to execute like this.
./mycalc 1 + 2
the sum of 1 plus 2 equals 3
But if it does not have any parameters a menu should display asking for subtraction, addition, or exit.
How would this layout look? I keep trying but whenever I run it it gives me errors saying I need to enter the parameters and then after the error the menu displays.
op="$2"
if [ $op == "+" ]
then
sum=$(expr $1 + $3)
echo "The sum of $1 plus $3 equals $sum"
elif [ $op == "-" ]
then
diff=$(expr $1 - $3)
echo "The sum of $1 minus $3 equals $diff"
else
while [ $ch != "X" ] || [ $ch != "x" ]
do
read -p "C) Calculation
X) Exit" ch
Here's a "cute" answer. I'll give you some feedback on your code later.
#!/bin/bash
case $2 in
+|-) :;;
*) echo "unknown operator: $2"; exit 1;;
esac
declare -A ops=(
["-"]=minus
["+"]=plus
)
printf "%d %s %d equals %d\n" "$1" "${ops["$2"]}" "$3" "$(bc <<< "$*")"
Here's a rewrite, hopefully demonstrating a few useful techniques and good practices
#!/bin/bash
user_args () {
case $2 in
+) echo "The sum of $1 plus $3 equals $(( $1 + $3 ))" ;;
-) echo "The sum of $1 minus $3 equals $(( $1 - $3 ))" ;;
*) echo "I don't know what to do with operator '$2'" ;;
esac
}
interactive () {
PS3="Select an operator: "
select choice in plus minus exit; do
case $choice in
plus) operator="+"; break ;;
minus) operator="-"; break ;;
exit) exit;;
esac
done
read -p "First value: " first
read -p "Second value: " second
echo "The sum of $first $choice $second equals $(( $first $operator $second ))"
}
if (( $# == 3 )); then
user_args "$#"
else
interactive
fi
use of functions for modularity
case statement for easily expanded branching
select statement to generate the menu and enforce valid input
bash's built-in arithmetic expressions
passing arguments with "$#"
quoting variables everywhere the need to be quoted
Command line parameters are referenced as $1, $2, $3...
($1 for first arg, $2 for second ...)
you can test if parameters are not null with :
if [ -z "$1" ]
then
echo "No argument supplied"
fi

When/how to use "==" or "-eq" operator in test?

In the following code I want to compare the command line arguments with the parameters but I am not sure what is the current syntax to compare the arguments with parameters..i.e "==" or "-eq".
#!/bin/bash
argLength=$#
#echo "arg = $1"
if [ argLength==0 ]; then
#Running for the very first
#Get the connected device ids and save it in an array
N=0
CONNECTED_DEVICES=$(adb devices | grep -o '\b[A-Za-z0-9]\{8,\}\b'|sed -n '2,$p')
NO_OF_DEVICES=$(echo "$CONNECTED_DEVICES" | wc -l)
for CONNECTED_DEVICE in $CONNECTED_DEVICES ; do
DEVICE_IDS[$N]="$CONNECTED_DEVICE"
echo "DEVICE_IDS[$N]= $CONNECTED_DEVICE"
let "N= $N + 1"
done
for SEND_DEVICE_ID in ${DEVICE_IDS[#]} ; do
callCloneBuildInstall $SEND_DEVICE_ID
done
elif [ "$1" -eq -b ]; then
if [ $5 -eq pass ]; then
DEVICE_ID=$3
./MonkeyTests.sh -d $DEVICE_ID
else
sleep 1h
callCloneBuildInstall $SEND_DEVICE_ID
fi
elif [ "$1" -eq -m ]; then
echo "Check for CloneBuildInstall"
if [ "$5" -eq pass ]; then
DEVICE_ID=$3
callCloneBuildInstall $SEND_DEVICE_ID
else
echo "call CloneBuildInstall"
# Zip log file and save it with deviceId
callCloneBuildInstall $SEND_DEVICE_ID
fi
fi
function callCloneBuildInstall {
./CloneBuildInstall.sh -d $SEND_DEVICE_ID
}
From help test:
[...]
STRING1 = STRING2
True if the strings are equal.
[...]
arg1 OP arg2 Arithmetic tests. OP is one of -eq, -ne,
-lt, -le, -gt, or -ge.
But in any case, each part of the condition is a separate argument to [.
if [ "$arg" -eq 0 ]; then
if [ "$arg" = 0 ]; then
Why not use something like
if [ "$#" -ne 0 ]; then # number of args should not be zero
echo "USAGE: "
fi
When/how to use “==” or “-eq” operator in test?
To put it simply use == when doing lexical comparisons a.k.a string comparisons but use -eq when having numerical comparisons.
Other forms of -eq (equal) are -ne (not equal), -gt (greater than), -ge (greater than or equal), -lt (lesser than), and -le (lesser than or equal).
Some may also suggest preferring (( )).
Examples:
[[ $string == "something else" ]]
[[ $string != "something else" ]] # (negated)
[[ $num -eq 1 ]]
[[ $num -ge 2 ]]
(( $num == 1 ))
(( $num >= 1 ))
And always use [[ ]] over [ ] when you're in Bash since the former skips unnecessary expansions not related to conditional expressions like word splitting and pathname expansion.

How to compare a variable with a variable minus a constant in a linux shell script?

I want to compare a variable with another variable minus a constant in a linux shell script.
In cpp this would look like this:
int index = x;
int max_num = y;
if (index < max_num - 1) {
// do whatever
} else {
// do something else
}
In the shell i tried the following:
index=0
max_num=2
if [ $index -lt ($max_num - 1) ]; then
sleep 20
else
echo "NO SLEEP REQUIRED"
fi
I also tried:
if [ $index -lt ($max_num-1) ]; then
...
if [ $index -lt $max_num - 1 ]; then
...
if [ $index -lt $max_num-1 ]; then
...
but non of these versions works.
How do you write such a condition correctly?
Regards
The various examples that you tried do not work because no arithmetic operation actually happens in any of the variants that you tried.
You could say:
if [[ $index -lt $((max_num-1)) ]]; then
echo yes
fi
$(( expression )) denotes Arithmetic Expression.
[[ expression ]] is a Conditional Construct.
Portably (plain sh), you could say
if [ "$index" -lt "$((max_num-1))" ]; then
echo yes
fi
Short version
[ "$index" -lt "$((max_num-1))" ] && echo yes;
[ is the test program, but requires the closing ] when called as [. Note the required quoting around variables. The quoting is not needed when using the redundant and inconsistent bash extensions cruft ([[ ... ]]).
In bash, a more readable arithmetic command is available:
index=0
max_num=2
if (( index < max_num - 1 )); then
sleep 20
else
echo "NO SLEEP REQUIRED"
fi
The strictly POSIX-compliant equivalent is
index=0
max_num=2
if [ "$index" -lt $((max_num - 1)) ]; then
sleep 20
else
echo "NO SLEEP REQUIRED"
fi

Resources