How to perform arithmetic in UNIX? - bash

I am trying to build a very basic 4 function arithmetic script in UNIX and it doesn't like my arithmetic statement. I was trying to use 'bash arithmetic' syntax
from this source
http://faculty.salina.k-state.edu/tim/unix_sg/bash/math.html
also as a side note when you reference a variable in UNIX when do you need to use the "$" symbol, and when do you not need to?
#!/bin/bash
str1="add"
str2="sub"
str3="div"
str4="mult"
((int3=0))
((int2=0))
((int1=0))
clear
read -p "please enter the first integer" $int1
clear
read -p "Please enter mathmatical operator'" input
clear
read -p "Please enter the second integer" $int2
if [ "$input" = "$str1" ];
then
((int3 = int1+int2))
echo "$int3"
else
echo "sadly, it does not work"
fi;

I think this is what you want:
#!/bin/bash
str1="add"
str2="sub"
str3="div"
str4="mult"
((int3=0)) # maybe you can explain me in comments why you need a arithmetic expression here to perform an simple assignment?
((int2=0))
((int1=0))
echo -n "please enter the first integer > "
read int1
echo -n "Please enter mathmatical operator > "
read input
echo -n "Please enter the second integer > "
read int2
if [ $input = $str1 ]
then
((int3=int1 + int2))
echo "$int3"
else
echo "sadly, it does not work"
fi
exec $SHELL
You should definitly checkout man bash. It is documented there in which command you need to specify $ or not to reference a variable. But aside from that:
var=123 # variable assignment. no spaces in between
echo $var # fetches/references the value of var. Or in other words $var gets substituted by it's value.

Use bc command
something like this
echo "9/3+12" | bc

You use $ when you want the value of the variable. read, though, expects the name of a variable:
read -p "..." int1
(Technically, you could do something like
name=int1
read -p "..." "$name"
to set the value of int1, because the shell expands name to the string int1, which read then uses as the name.)

Here's a quick once over:
op=( add sub div mult )
int1=0
int2=0
ans=0
clear
read -p "please enter the first integer > " int1
clear
IFS='/'
read -p "Please enter mathmatical operator (${op[*]})> " input
unset IFS
clear
read -p "Please enter the second integer > " int2
case "$input" in
add) (( ans = int1 + int2 ));;
sub) (( ans = int1 - int2 ));;
div) (( ans = int1 / int2 ));; # returns truncated value and might want to check int2 != 0
mult) (( ans = int1 * int2 ));;
*) echo "Invalid choice"
exit 1;;
esac
echo "Answer is: $ans"
You will also want to check that the user enters numbers to :)

Another one
declare -A oper
oper=([add]='+' [sub]='-' [div]='/' [mul]='*')
read -r -p 'Num1? > ' num1
read -r -p "oper? (${!oper[*]}) > " op
read -r -p 'Num2? > ' num2
[[ -n "${oper[$op]}" ]] || { echo "Err: unknown operation $op" >&2 ; exit 1; }
res=$(bc -l <<< "$num1 ${oper[$op]} $num2")
echo "$num1 ${oper[$op]} $num2 = $res"

Related

what code function will help to ask for input when i try to execute a code with no previous value in shell script

When I choose Option 3 after opening the file it just terminates.
I was trying to use if else function inside section 3 where it ask for new values if there is none stored so that instead of terminating it will ask for values but cant seem to work it out.
#!/bin/bash
while : #This program demonstrate 4 option below
do
clear
echo "Main Menu"
echo "Select any option of your choice"
echo "[1] Show Todays date/time,Files in current directory,home
directory,user id "
echo "[2] Enter a range "
echo "[3] Highest and lowest of the eight random numbers "
echo "[4] Exit/Stop "
echo "==========="
echo -n "Menu choice [1-4]: "
read -r yourch #Choose option out of 4
case $yourch in
1) echo "Today is";date;
echo "Your home directory is:";home;
echo "Your path is :";PWD;
echo "Current Shell";uname;
echo "Your Student ID $USER ID ";
echo "Press a key...";read -r;;
2) echo "Lower value" #Enter the lower value
read -r s1
echo "Higher value" #Enter the higher value
read -r s2
dif=$((s2-s1))
if [ $dif -ne 100 ]
then
echo "Range should be 100"
else #if the differnce is 100 then programe run otherwise terminates
in=$( ("$s2" - "$s1")) #formula for the range
echo "8 random numbers between $s1 and $s2 are :-"
for i in $(seq 1 8)
do
t=$( ($RANDOM % "$in"))
n=$( ("$t" + "$s1"))
echo "$n" #Here we get the random numbers
done
fi
echo "Press a key..."; read -r;;
3) diff=$((s2 - s1)) #Depicts Highest and lowest numbers of the randoms
RANDOM=$$
min=9999
max=-1
for i in $(seq 8)
do
R=$((((RANDOM%diff))+s1))
if [[ "$R" -gt "$max" ]]
then
max=$R
fi
if [[ "$R" -lt "$min" ]]
then
min=$R
fi
done
echo "Biggest number and smallest numbers are $max and $min" #Prints the highest and lowest numbers
echo "press a key...";read -r;;
4)echo " THANK YOU VERY MUCH $ Good Bye"
exit 0;; #Exit command
*)echo "Opps!!! Please select choice 1,2,3,4";
echo "press a key...";read -r;;
esac
done
I would like for it to ask for new values if there is no previous data stored.
I checked your script, to see the problem. It terminates with a division by zero, because s1 and s2 initially are not set. To resolve this, you can use code like
if [ -z "${s1}" ] ;then
read -p "s1 is empty, please enter a number " s1
fi
if [ -z "${s2}" ] ;then
read -p "s2 is empty, please enter a number " s2
fi
-z "..." is true, if the string is empty. The shell doesn't distinguish data types and because I use the doublequotes it is safe to check for an empty string because if s1 is not set, "$s1" results in an empty string.
Btw. "$s1" is logically equivalent to "${s1}", but it is safer to use the curly braces, because there are no ambiguities this way where the variable ends. For example consider the lines:
year=90
echo "I like the music of the $years"
#
echo "I like the music of the ${year}s"
The first echo outputs "I like the music of the" unless variable "years" was set before, while the second outputs "I like the music of the 90s". Without curly braces this would be a bit more inconvenient. Without curly braces sometimes you might run in such ambiguities, without recognizing it easily.

Change variable named in argument to bash function [duplicate]

This question already has answers here:
Dynamic variable names in Bash
(19 answers)
How to use a variable's value as another variable's name in bash [duplicate]
(6 answers)
Closed 5 years ago.
In my bash scripts, I often prompt users for y/n answers. Since I often use this several times in a single script, I'd like to have a function that checks if the user input is some variant of Yes / No, and then cleans this answer to "y" or "n". Something like this:
yesno(){
temp=""
if [[ "$1" =~ ^([Yy](es|ES)?|[Nn][Oo]?)$ ]] ; then
temp=$(echo "$1" | tr '[:upper:]' '[:lower:]' | sed 's/es//g' | sed 's/no//g')
break
else
echo "$1 is not a valid answer."
fi
}
I then would like to use the function as follows:
while read -p "Do you want to do this? " confirm; do # Here the user types "YES"
yesno $confirm
done
if [[ $confirm == "y" ]]; then
[do something]
fi
Basically, I want to change the value of the first argument to the value of $confirm, so that when I exit the yesno function, $confirm is either "y" or "n".
I tried using set -- "$temp" within the yesnofunction, but I can't get it to work.
You could do it by outputting the new value and overwriting the variable in the caller.
yesno() {
if [[ "$1" =~ ^([Yy](es|ES)?|[Nn][Oo]?)$ ]] ; then
local answer=${1,,}
echo "${answer::1}"
else
echo "$1 is not a valid answer." >&2
echo "$1" # output the original value
return 1 # indicate failure in case the caller cares
fi
}
confirm=$(yesno "$confirm")
However, I'd recommend a more direct approach: have the function do the prompting and looping. Move all of that repeated logic inside. Then the call site is super simple.
confirm() {
local prompt=$1
local reply
while true; do
read -p "$prompt" reply
case ${reply,,} in
y*) return 0;;
n*) return 1;;
*) echo "$reply is not a valid answer." >&2;;
esac
done
}
if confirm "Do you want to do this? "; then
# Do it.
else
# Don't do it.
fi
(${reply,,} is a bash-ism that converts $reply to lowercase.)
You could use the nameref attribute of Bash (requires Bash 4.3 or newer) as follows:
#!/bin/bash
yesno () {
# Declare arg as reference to argument provided
declare -n arg=$1
local re1='(y)(es)?'
local re2='(n)o?'
# Set to empty and return if no regex matches
[[ ${arg,,} =~ $re1 ]] || [[ ${arg,,} =~ $re2 ]] || { arg= && return; }
# Assign "y" or "n" to reference
arg=${BASH_REMATCH[1]}
}
while read -p "Prompt: " confirm; do
yesno confirm
echo "$confirm"
done
A sample test run looks like this:
Prompt: YES
y
Prompt: nOoOoOo
n
Prompt: abc
Prompt:
The expressions are anchored at the start, so yessss etc. all count as well. If this is not desired, an end anchor ($) can be added.
If neither expression matches, the string is set to empty.

Storing output of here-string BC calculation into a variable for error-checking

I have an assignment for school which is to create a script that can calculate a math equation any length using order of operations. I had some trouble with this and ended up finding out about here-strings. The biggest problem with the script seems to be error-checking.
I tried to check the output of bc using $?, however it is 0 whether it is success or fail. In response to that I am now attempting to store the output of the here-string into a variable, and then I will use regex to check if output begins with a number. Here is the piece of code I wish to store in a variable, followed by the rest of my script.
#!/bin/bash
set -f
#the here-string bc command I wish to store output into variable
cat << EOF | bc
scale=2
$*
EOF
read -p "Make another calculation?" response
while [ $response = "y" ];do
read -p "Enter NUMBER OPERATOR NUMBER" calc1
cat << EOF | bc
scale=2
$calc1
EOF
read -p "Make another calculation?" response
done
~
This should do the trick:
#!/bin/sh
while read -p "Make another calculation? " response; [ "$response" = y ]; do
read -p "Enter NUMBER OPERATOR NUMBER: " calc1
result=$(bc << EOF 2>&1
scale=2
$calc1
EOF
)
case $result in
([0-9]*)
printf '%s\n' "$calc1 = $result";;
(*)
printf '%s\n' "Error, exiting"; break;;
esac
done
Sample run:
$ ./x.sh
Make another calculation? y
Enter NUMBER OPERATOR NUMBER: 5+5
5+5 = 10
Make another calculation? y
Enter NUMBER OPERATOR NUMBER: 1/0
Error, exiting
Note that you might do this without a here-document like this:
result=$(echo "scale=2; $calc1" | bc 2>&1)

how to write a Bash function that confirms the value of an existing variable with a user

I have a large number of configuration variables for which I want users to issue confirmation of the values. So, there could be some variable specifying a run number in existence and I want the script to ask the user if the current value of the variable is ok. If the user responds that the value is not ok, the script requests a new value and assigns it to the variable.
I have made an initial attempt at a function for doing this, but there is some difficulty with its running; it stalls. I would value some assistance in solving the problem and also any criticisms of the approach I'm using. The code is as follows:
confirmVariableValue(){
variableName="${1}"
variableValue="${!variableName}"
while [[ "${userInput}" != "n" && "${userInput}" != "y" ]]; do
echo "variable "${variableName}" value: "${variableValue}""
echo "Is this correct? (y: continue / n: change it / other: exit)"
read userInput
# Make the user input lowercase.
userInput="$(echo "${userInput}" | sed 's/\(.*\)/\L\1/')"
# If the user input is "n", request a new value for the variable. If the
# user input is anything other than "y" or "n", exit. If the user input
# is "y", then the user confirmation loop ends.
if [[ "${userInput}" == "n" ]]; then
echo "enter variable "${variableName}" value:"
read variableValue
elif [[ "${userInput}" != "y" && "${userInput}" != "n" ]]; then
echo "terminating"
exit 0
fi
done
echo "${variableValue}"
}
myVariable="run_2014-09-23T1909"
echo "--------------------------------------------------------------------------------"
echo "initial variable value: "${myVariable}""
myVariable="$(confirmVariableValue "myVariable")"
echo "final variable value: "${myVariable}""
echo "--------------------------------------------------------------------------------"
The problem is here:
myVariable="$(confirmVariableValue "myVariable")"
your questions, like
echo "Is this correct? (y: continue / n: change it / other: exit)"
are going into the myVariable and not to the screen.
Try print questions to STDERR, or any other file-descriptor but STDOUT.
Opinion based comment: I would be unhappy with such config-script. It is way too chatty. For me is better:
print out the description and the default value
and ask Press Enter for confirm or enter a new value or <something> for exit>
You can also, use the following technique:
use the bash readline library for the read command with -e
use the -i value for set the default value for the editing
use the printf -v variable to print into variable, so you don't need to use var=$(...) nor any (potentially) dangerous eval...
example:
err() { echo "$#" >&2; return 1; }
getval() {
while :
do
read -e -i "${!1}" -p "$1>" inp
case "$inp" in
Q|q) err "Quitting...." || return 1 ;;
"") err "Must enter some value" ;;
*)
#validate the input here
#and print the new value into the variable
printf -v "$1" "%s" "$inp"
return 0
;;
esac
done
}
somevariable=val1
anotherone=val2
x=val3
for var in somevariable anotherone x
do
getval "$var" || exit
echo "new value for $var is: =${!var}="
done
I would not have them answer "Yes" then type in the new value. Just have them type in the new value if they want one, or leave it blank to accept the default.
This little function lets you set multiple variables in one call:
function confirm() {
echo "Confirming values for several variables."
for var; do
read -p "$var = ${!var} ... leave blank to accept or enter a new value: "
case $REPLY in
"") # empty use default
;;
*) # not empty, set the variable using printf -v
printf -v "$var" "$REPLY"
;;
esac
done
}
Used like so:
$ foo='foo_default_value'
$ bar='default_for_bar'
$ confirm foo bar
Confirming values for several variables.
foo = foo_default_value ... leave blank to accept or enter a new value: bar
bar = default_for_bar ... leave blank to accept or enter a new value:
foo=[bar], bar=[default_for_bar]
Of course, if blank can be a default, then you would need to account for that, like #jm666 use of read -i.

Bash Shell Scripting - detect the Enter key

I need to compare my input with Enter/Return key...
read -n1 key
if [ $key == "\n" ]
echo "###"
fi
But this is not working.. What is wrong with this code
Several issues with the posted code. Inline comments detail what to fix:
#!/bin/bash
# ^^ Bash, not sh, must be used for read options
read -s -n 1 key # -s: do not echo input character. -n 1: read only 1 character (separate with space)
# double brackets to test, single equals sign, empty string for just 'enter' in this case...
# if [[ ... ]] is followed by semicolon and 'then' keyword
if [[ $key = "" ]]; then
echo 'You pressed enter!'
else
echo "You pressed '$key'"
fi
Also it is good idea to define empty $IFS (internal field separator) before making comparisons, because otherwise you can end up with " " and "\n" being equal.
So the code should look like this:
# for distinguishing " ", "\t" from "\n"
IFS=
read -n 1 key
if [ "$key" = "" ]; then
echo "This was really Enter, not space, tab or something else"
fi
I'm adding below code just for reference if someone will want to use such solution containing countdown loop.
IFS=''
echo -e "Press [ENTER] to start Configuration..."
for (( i=10; i>0; i--)); do
printf "\rStarting in $i seconds..."
read -s -N 1 -t 1 key
if [ "$key" = $'\e' ]; then
echo -e "\n [ESC] Pressed"
break
elif [ "$key" == $'\x0a' ] ;then
echo -e "\n [Enter] Pressed"
break
fi
done
read reads a line from standard input, up to but not including the new line at the end of the line. -n specifies the maximum number of characters, forcing read to return early if you reach that number of characters. It will still end earlier however, when the Return key is pressed. In this case, its returning an empty string - everything up to but not including the Return key.
You need to compare against the empty string to tell if the user immediately pressed Return.
read -n1 KEY
if [[ "$KEY" == "" ]]
then
echo "###";
fi
None of these conditions worked for me and so I've came up with this one:
${key} = $'\0A'
Tested on CentOS with Bash 4.2.46.

Resources