When running bash script there is a logical error - bash

So I have fixed my first issue with the script taking input but now no matter what letter I enter it only adds the numbers.
here is the code. any help would be greatly appreciated.
#!/bin/bash
add() {
expr $x + $y
}
sub() {
expr $x - $y
}
mult() {
expr $x * $y
}
div() {
expr $x / $y
}
echo "Enter a for add, s for subtract, m for multiply or d for divide and 2 numbers"
read choice x y
if [ $choice=="a" ]
then
add
else
if [ $choice == "s" ]
then
sub
else
if [ $choice == "m" ]
then
mult
else
if [ $choice == "d" ]
then
div
fi
fi
fi
fi

It seems like you want to get x and y from first and second arguments ( $1 and $2 ) and to read the operation ( a, s, d, m ) from stdin.
I modified your code a bit, to overcome the problems in the original script and to provide the result based on my assumptions:
#!/bin/bash
# First number.
x=$1
# Second number.
y=$2
# Result of either addition, subtraction, division or multiplication of $x and $y.
result=0
# Reads operation from user.
read -ep "Enter a for add, s for subtract, m for multiply or d for divide: " operation
case $operation in
a) result=$(( x + y ));;
s) result=$(( x - y ));;
d) result=$(( x / y ));;
m) result=$(( x * y ));;
*) printf '%s: %s\n' "$operation" "Unknown operation" >&2; exit 1;;
esac
printf 'result: %s\n' "$result"
Usage example: ( script name is sof.sh )
./sof.sh 5 4
Enter a for add, s for subtract, m for multiply or d for divide: a
result: 9
./sof.sh 5 4
Enter a for add, s for subtract, m for multiply or d for divide: m
result: 20
./sof.sh 5 4
Enter a for add, s for subtract, m for multiply or d for divide: s
result: 1
./sof.sh 5 4
Enter a for add, s for subtract, m for multiply or d for divide: d
result: 1
P.S.
Please note the following:
expr is a program used in ancient shell code to do math. In POSIX shells like bash, use $(( expression )). In bash, ksh88+, mksh/pdksh, or zsh, you can also use (( expression )) or 'let
expression'.
Though not used originaly in the script, while programming in Bash it is worth knowing that [[ is a bash keyword similar to (but more powerful than) the [ command. See this Bash FAQ and Test and conditionals.
Unless you're writing for POSIX sh, it is recommended to use [[.

First of all, you want the script to read the values from the standard input, but you are recovering it from the arguments.
Second, you are not passing parameters to the functions.
Third, you are not using parameters inside the functions.
Fourth, you are not letting spaces between operators when using expr.
NOTE: Rany Albeg Wein remarked that this bash guide is outdated, and he recommends this one. Also i recommend the GNU official guide (other formats).
So, assuming that you want to use your script like ./my-script.sh m 2 3 , here is your code, but working:
#!/bin/bash
add() {
expr $1 + $2
}
sub() {
expr $1 - $2
}
mult() {
expr $1 \* $2
}
div() {
expr $1 / $2
}
echo "Enter a for add, s for subtract, m for multiply or d for divide and 2 numbers"
x=$2
y=$3
if [ $1 == "a" ]
then
add $x $y
else
if [ $1 == "s" ]
then
sub $x $y
else
if [ $1 == "m" ]
then
mult $x $y
else
if [ $1 == "d" ]
then
div $x $y
fi
fi
fi
fi
And finally this is your script minimally modified to read the data from the standard input:
#!/bin/bash
add() {
echo "Result:"
expr $1 + $2
}
sub() {
echo "Result:"
expr $1 - $2
}
mult() {
echo "Result:"
expr $1 \* $2
}
div() {
echo "Result:"
expr $1 / $2
}
echo "Enter a for add, s for subtract, m for multiply or d for divide and 2 numbers"
read operation
echo "Read first parameter"
read x
echo "Read second parameter"
read y
if [ $operation == "a" ]
then
add $x $y
else
if [ $operation == "s" ]
then
sub $x $y
else
if [ $operation == "m" ]
then
mult $x $y
else
if [ $operation == "d" ]
then
div $x $y
fi
fi
fi
fi
Also, if you had some troubles, you could add debugging messages to the script just setting #!/bin/bash -xv at the beginning of the script.

Related

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

Bash script ignores the negative prefix on negative numbers

a=4;
b=7;
c=5;
x =[ a-b ]
if (x -gt c) then {
echo "x is greater"
} else {
echo " something"
}
I want to compare x and c ignoring the negative prefix of c.
I'm assuming you meant "negative prefix of x". There are a ton of errors in your code, are you sure you're writing in bash?
#!/bin/bash
typeset a=4 b=7 c=5
x=$(( a - b ))
x=${x//-/}
if [[ x -gt c ]]; then
echo "x is greater"
else
echo " something"
fi

Why is my bash script creating files without telling it to?

I am running the following script, which has a function intended to tell me whether one date is before another, as seen at the very bottom of the script.
Now, the script has a few bugs. But one of them in particular is strange. The script creates files named by the dates that are inputted by the last argument.
It creates files called "09", "12", and "2015". Why are these files created? Here's the function. You'll notice the last few lines which call the function with inputs
function compare_two {
if [ $1 < $2 ];
then
return 2
elif [ $1 > $2 ];
then
return 3
else
return 4
fi
}
function compare_dates {
# two input arguments:
# e.g. 2015-09-17 2011-9-18
date1=$1
date2=$2
IFS="-"
test=( $date1 )
Y1=${test[0]}
M1=${test[1]}
D1=${test[2]}
test=( $date2 )
Y2=${test[0]}
M2=${test[1]}
D2=${test[2]}
compare_two $Y1 $Y2
if [ $? == 2 ];
then
echo "returning 2"
return 2
elif [ $? == 3 ];
then
return 3
else
compare_two $M1 $M2;
if [ $? == 2 ];
then
echo "returning 2"
return 2
elif [ $? == 3 ];
then
return 3
else
compare_two $D1 $D2;
if [ $? == 2 ];
then
echo $?
echo "return 2"
return 2
elif [ $? == 3 ];
then
echo "returning 3"
return 3
else
return 4
fi
fi
fi
}
compare_dates 2015-09-17 2015-09-12
echo $?
the result doesn't throw an error, but rather outputs
returning 2
2
The result is incorrect, I'm aware. But I'll fix that later. What is creating these files and how do I stop it? Thanks.
the lower and greater sign are interpreted as redirections.
type man test and find out the right syntax
Your problem is with the [ $1 < $2 ], the < is being understood as a redirection character.
The solution is to use any of this alternatives:
[ $1 \< $2 ]
[ $1 -lt $2 ]
(( $1 < $2 )) # works in bash.
The [[ $1 < $2 ]] is NOT an integer compare, but (from the manual):
« the < and > operators sort lexicographically using the current locale »
I'll recommend to use the (( $1 < $2 )) option.
to avoid a problem that some numbers (the ones that start with a zero) like 08 cause a problem when compared in an arithmetic expansion, use:
(( 10#$1 < 10#$2 ))
(( 10#$1 > 10#$2 ))
to force a base 10 for the numbers.
However, if possible, using GNU date is a lot easier IMhO (it transform Year/Month/Day into one single number to compare: seconds since epoch):
a=$(date -d '2015-09-17' '+%s');
b=$(date -d '2015-09-12' '+%s');
compare_two "$a" "$b"

getting this error "integer expression expected : line 10 and 23" in this bash shell

I'm trying to check a number is palindromic or not. I used a function called pal(). I'm getting this error that says:
./pal.sh: line 10: [: input: integer expression expected
./pal.sh: line 23: [: input: integer expression expected
My code:
#!/bin/bash
pal()
{
num=$1
rnum=$num
add=0
k=0
while [ $num -ne 0 ]
do
lev=1
mod= $num % 10
for((i=0;i<$k;i++))
do
lev=`expr $lev \* 10`
done
mul=`expr $mod \* $lev`
add=`expr $add + $mul`
num=`expr $num / 10`
k=`expr $k + 1`
done
if [ $rnum -eq $add ]
then
echo "pallindrome"
else
echo "not pallindrome"
fi
}
echo "input number"
read input
pal input
You need to pass the value of the variable, not its name:
pal $input
Also, as anubhava points out in a comment, you will need to fix the arithmetic in the function:
mod= $num % 10
should be:
mod=$(($num % 10))
or:
((mod = $num % 10))
You should also generally avoid using expr in bash — there are built-in facilities to handle pretty much anything expr can handle.
You can do this with the rev from "util-linux" like so,
#!/bin/bash
pal()
{
input=$1
if [ $input == $(echo $input | rev) ]; then
echo "pallindrome"
else
echo "not pallindrome"
fi
}
echo "input number"
read input
pal $input
Output
$ ./pal.sh
input number
101
pallindrome
$ ./pal.sh
input number
102
not pallindrome

bash - increment variables that contain letters

I have a set of valid characters [0-9a-z_] and a variable that is assigned one of these characters. What I want to do is to be able to increment that variable to the next in the set.
If need be I can handle the "special" cases where it would increment from '9' to 'a' and 'z' to '_', but I can't figure out how to increment letters.
#!/bin/bash
y=b
echo $y # this shows 'b'
y=$((y+1))
echo $y # this shows '1', but I want it to be 'c'
y=b
echo "$y" # this shows 'b'
y=$(echo "$y" | tr "0-9a-z" "1-9a-z_")
echo "$y" # this shows 'c'
Note that this does not handle the case where $y = "_" (not sure what you want then, and in any case it'll probably require separate handling), and if $y is more than one character long it'll "increment" all of them (i.e. "10" -> "21", "09g" -> "1ah", etc).
Maybe this can be a solution:
a=({0..9} {a..z} _)
echo ${a[*]}
yc=11
echo ${a[yc]}
((++yc))
echo ${a[yc]}
echo ${a[++yc]}
#Alternative
declare -A h
# Fill the has point to the next character
for((i=0;((i+1))<${#a[*]};++i)) { h[${a[i]}]=${a[i+1]};}
y=b
echo $y, ${h[$y]}
Output:
0 1 2 3 4 5 6 7 8 9 a b c d e f g h i j k l m n o p q r s t u v w x y z _
b
c
d
b, c
for those who would like to print incremented letter by execution of a function:
ALPHA=( {A..Z} )
alpha_increment () { echo ${ALPHA[${i:-0}]}; ((i++)) ;}
alpha_increment
A
alpha_increment
B
alpha_increment
C
You can start with this:
echo 0x$(( $(printf "%x" "'b'") + 1)) | xxd -r
I wrote this for a project, it uses the chr and ord fucntions(also found here somewhere) and some pure bash(only external called in the function is tr), if you are doing more than 100 characters I would use something else, but for short strings in my testing its actually slightly faster than python.
Also this script lower cases any input, you will have to modify it for upper case.
after putting these functions in your script(or cut and paste into a shell) you can just do
inc_string abz9z9
and get back.
aca0a0
chr() {
[ "$1" -lt 256 ] || return 1
printf "\\$(printf '%03o' "$1")"
}
ord() {
LC_CTYPE=C printf '%d' "'$1"
}
inc_string ()
{
string="$1";
lcstring=$(echo $string | tr '[:upper:]' '[:lower:]');
for ((position=$((${#lcstring}-1));position>=0;position--));do
if [ "${lcstring:position:1}" = 'z' ]; then
if [ "$position" -eq "$((${#lcstring}-1))" ]; then
newstring="${lcstring:0:$(($position))}a";
lcstring="$newstring";
elif [ "$position" -eq "0" ]; then
newstring="a${lcstring:$((position+1))}";
echo $newstring;
break;
else
newstring="${lcstring:0:$(($position))}a${lcstring:$((position+1))}";
lcstring="$newstring";
fi
elif [ "${lcstring:position:1}" = '9' ]; then
if [ "$position" -eq "$((${#lcstring}-1))" ]; then
newstring="${lcstring:0:$(($position))}0";
lcstring="$newstring";
elif [ "$position" -eq "0" ]; then
newstring="0${lcstring:$((position+1))}";
echo $newstring;
break;
else
newstring="${lcstring:0:$(($position))}0${lcstring:$((position+1))}";
lcstring="$newstring";
fi
else
if [ "$position" -eq "$((${#lcstring}-1))" ]; then
newstring="${lcstring:0:$(($position))}$(chr $(($(ord ${lcstring:position})+1)))";
echo $newstring;
break;
elif [ "$position" -eq "0" ]; then
newstring="$(chr $(($(ord ${lcstring:position})+1)))${lcstring:$((position+1))}";
echo $newstring;
break;
else
newstring="${lcstring:0:$(($position))}$(chr $(($(ord ${lcstring:position})+1)))${lcstring:$(($position+1))}";
echo $newstring;
break;
fi
fi
done
}

Resources