Currently stuck in a situation where I ask the user to input a line of numbers with a space in between, then have the program display those numbers with a delay, then add them. I have everything down, but can't seem to figure out a line of code to coherently calculate the sum of their input, as most of my attempts end up with an error, or have the final number multiplied by the 2nd one (not even sure how?). Any help is appreciated.
echo Enter a line of numbers to be added.
read NUMBERS
COUNTER=0
for NUM in $NUMBERS
do
sleep 1
COUNTER=`expr $COUNTER + 1`
if [ "$NUM" ]; then
echo "$NUM"
fi
done
I've tried echo expr $NUM + $NUM to little success, but this is really all I can some up with.
Start with
NUMBERS="4 3 2 6 5 1"
echo $NUMBERS
Your script can be changed into
sum=0
for NUM in ${NUMBERS}
do
sleep 1
((counter++))
(( sum += NUM ))
echo "Digit ${counter}: Sum=$sum"
done
echo Sum=$sum
Another way is using bc, usefull for input like 1.6 2.3
sed 's/ /+/g' <<< "${NUMBERS}" | bc
Set two variables n and m, store their sum in $x, print it:
n=5 m=7 x=$((n + m)) ; echo $x
Output:
12
The above syntax is POSIX compatible, (i.e. works in dash, ksh, bash, etc.); from man dash:
Arithmetic Expansion
Arithmetic expansion provides a mechanism for evaluating an arithmetic
expression and substituting its value. The format for arithmetic expanâ
sion is as follows:
$((expression))
The expression is treated as if it were in double-quotes, except that a
double-quote inside the expression is not treated specially. The shell
expands all tokens in the expression for parameter expansion, command
substitution, and quote removal.
Next, the shell treats this as an arithmetic expression and substitutes
the value of the expression.
Two one-liners that do most of the job in the OP:
POSIX:
while read x ; do echo $(( $(echo $x | tr ' ' '+') )) ; done
bash:
while read x ; do echo $(( ${x// /+} )) ; done
bash with calc, (allows summing real, rational & complex numbers, as well as sub-operations):
while read x ; do calc -- ${x// /+} ; done
Example input line, followed by output:
-8!^(1/3) 2^63 -1
9223372036854775772.7095244707464171953
Related
I have this Bash script and I had a problem in line 16.
How can I take the previous result of line 15 and add
it to the variable in line 16?
#!/bin/bash
num=0
metab=0
for ((i=1; i<=2; i++)); do
for j in `ls output-$i-*`; do
echo "$j"
metab=$(cat $j|grep EndBuffer|awk '{sum+=$2} END { print sum/120}') (line15)
num= $num + $metab (line16)
done
echo "$num"
done
For integers:
Use arithmetic expansion: $((EXPR))
num=$((num1 + num2))
num=$(($num1 + $num2)) # Also works
num=$((num1 + 2 + 3)) # ...
num=$[num1+num2] # Old, deprecated arithmetic expression syntax
Using the external expr utility. Note that this is only needed for really old systems.
num=`expr $num1 + $num2` # Whitespace for expr is important
For floating point:
Bash doesn't directly support this, but there are a couple of external tools you can use:
num=$(awk "BEGIN {print $num1+$num2; exit}")
num=$(python -c "print $num1+$num2")
num=$(perl -e "print $num1+$num2")
num=$(echo $num1 + $num2 | bc) # Whitespace for echo is important
You can also use scientific notation (for example, 2.5e+2).
Common pitfalls:
When setting a variable, you cannot have whitespace on either side of =, otherwise it will force the shell to interpret the first word as the name of the application to run (for example, num= or num)
num= 1 num =2
bc and expr expect each number and operator as a separate argument, so whitespace is important. They cannot process arguments like 3+ +4.
num=`expr $num1+ $num2`
Use the $(( )) arithmetic expansion.
num=$(( $num + $metab ))
See Chapter 13. Arithmetic Expansion for more information.
There are a thousand and one ways to do it. Here's one using dc (a reverse Polish desk calculator which supports unlimited precision arithmetic):
dc <<<"$num1 $num2 + p"
But if that's too bash-y for you (or portability matters) you could say
echo $num1 $num2 + p | dc
But maybe you're one of those people who thinks RPN is icky and weird; don't worry! bc is here for you:
bc <<< "$num1 + $num2"
echo $num1 + $num2 | bc
That said, there are some unrelated improvements you could be making to your script:
#!/bin/bash
num=0
metab=0
for ((i=1; i<=2; i++)); do
for j in output-$i-* ; do # 'for' can glob directly, no need to ls
echo "$j"
# 'grep' can read files, no need to use 'cat'
metab=$(grep EndBuffer "$j" | awk '{sum+=$2} END { print sum/120}')
num=$(( $num + $metab ))
done
echo "$num"
done
As described in Bash FAQ 022, Bash does not natively support floating point numbers. If you need to sum floating point numbers the use of an external tool (like bc or dc) is required.
In this case the solution would be
num=$(dc <<<"$num $metab + p")
To add accumulate possibly-floating-point numbers into num.
In Bash,
num=5
x=6
(( num += x ))
echo $num # ==> 11
Note that Bash can only handle integer arithmetic, so if your AWK command returns a fraction, then you'll want to redesign: here's your code rewritten a bit to do all math in AWK.
num=0
for ((i=1; i<=2; i++)); do
for j in output-$i-*; do
echo "$j"
num=$(
awk -v n="$num" '
/EndBuffer/ {sum += $2}
END {print n + (sum/120)}
' "$j"
)
done
echo "$num"
done
I always forget the syntax so I come to Google Search, but then I never find the one I'm familiar with :P. This is the cleanest to me and more true to what I'd expect in other languages.
i=0
((i++))
echo $i;
I really like this method as well. There is less clutter:
count=$[count+1]
#!/bin/bash
read X
read Y
echo "$(($X+$Y))"
You should declare metab as integer and then use arithmetic evaluation
declare -i metab num
...
num+=metab
...
For more information, see 6.5 Shell Arithmetic.
Use the shell built-in let. It is similar to (( expr )):
A=1
B=1
let "C = $A + $B"
echo $C # C == 2
Source: Bash let builtin command
Another portable POSIX compliant way to do in Bash, which can be defined as a function in .bashrc for all the arithmetic operators of convenience.
addNumbers () {
local IFS='+'
printf "%s\n" "$(( $* ))"
}
and just call it in command-line as,
addNumbers 1 2 3 4 5 100
115
The idea is to use the Input-Field-Separator(IFS), a special variable in Bash used for word splitting after expansion and to split lines into words. The function changes the value locally to use word-splitting character as the sum operator +.
Remember the IFS is changed locally and does not take effect on the default IFS behaviour outside the function scope. An excerpt from the man bash page,
The shell treats each character of IFS as a delimiter, and splits the results of the other expansions into words on these characters. If IFS is unset, or its value is exactly , the default, then sequences of , , and at the beginning and end of the results of the previous expansions are ignored, and any sequence of IFS characters not at the beginning or end serves to delimit words.
The "$(( $* ))" represents the list of arguments passed to be split by + and later the sum value is output using the printf function. The function can be extended to add scope for other arithmetic operations also.
#!/usr/bin/bash
#integer numbers
#===============#
num1=30
num2=5
echo $(( num1 + num2 ))
echo $(( num1-num2 ))
echo $(( num1*num2 ))
echo $(( num1/num2 ))
echo $(( num1%num2 ))
read -p "Enter first number : " a
read -p "Enter second number : " b
# we can store the result
result=$(( a+b ))
echo sum of $a \& $b is $result # \ is used to espace &
#decimal numbers
#bash only support integers so we have to delegate to a tool such as bc
#==============#
num2=3.4
num1=534.3
echo $num1+$num2 | bc
echo $num1-$num2 | bc
echo $num1*$num2 |bc
echo "scale=20;$num1/$num2" | bc
echo $num1%$num2 | bc
# we can store the result
#result=$( ( echo $num1+$num2 ) | bc )
result=$( echo $num1+$num2 | bc )
echo result is $result
##Bonus##
#Calling built in methods of bc
num=27
echo "scale=2;sqrt($num)" | bc -l # bc provides support for calculating square root
echo "scale=2;$num^3" | bc -l # calculate power
#!/bin/bash
num=0
metab=0
for ((i=1; i<=2; i++)); do
for j in `ls output-$i-*`; do
echo "$j"
metab=$(cat $j|grep EndBuffer|awk '{sum+=$2} END { print sum/120}') (line15)
let num=num+metab (line 16)
done
echo "$num"
done
Works on MacOS. bc is a command line calculator
#!/bin/bash
sum=0
for (( i=1; i<=5; i++ )); do
sum=$(echo "$sum + 1.1" | bc) # bc: if you want to use decimal
done
echo "Total: $sum"
Write a script that expects a file as its first argument. Some lines of the
file will consist of integers 0 - 1000.
The script should select the lines matching the previous criteria and print out their average to stdout (average of n integers is their sum divided by n).
And the file given looks like this:
22
78907
77 88 99 0000
need 11 gallons of water
0
roses are red
11
Example output:
11
Explanation: (22 + 11 + 0) / 3 = 11
I have tried already with this code:
#!/bin/bash
sum=0
ind=0
while IFS='' read -r line || [[ -n "$line" ]]; do
if [[ $line =~ ^[a-zA-Z\ ]+$ ]]
then
${sum}=${sum}+${#line}
${ind}=${ind}+1
echo ${sum}
fi
done < "$1"
value=${sum}/${ind}
echo ${value}
the print of this code is always 0/0 and some errors like:
./test1: line 9: 0=0+13: command not found
./test1: line 10: 0=0+1: command not found
Any ideas?
Part of the issue with your script is answered here.. Your variable assignments are incorrect. You only use the $ to refer to a variable that has already been assigned. The assignment process drops the dollar sign.
The other issue you're having is that your arithmetic is not being expressed within an arithmetic expression.
Note that you can use use arithmetic expansion to handle your variables:
if [[ $line =~ ^[a-zA-Z\ ]+$ ]]; then
(( sum += ${#line} ))
(( ind++ ))
printf '%s\n' "$sum"
fi
and later ...
value="$(( sum / ind ))"
printf '%s\n' "$value"
Beware that bash can only deal with integer math, floats are truncated. For more advanced math, consider using bc or dc (which are not built in to bash, they are separate tools that may need to be installed on your system) or another language like awk or perl which can do the same thing with better performance and more precise math.
That said, you can "fake" a couple of decimal places with a few extra lines of code and string manipulation, if you really need to:
$ sum=100; ind=7
$ printf -v x '%d' "$((${sum}00/${ind}))"
$ printf '%d.%d\n' "${x%??}" "${x:$((${#x}-2))}"
14.28
The first printf has division which multiplies the dividend by 100 (by adding two zeroes after it). The resultant quotient is then split with the second printf to insert the decimal point. This is a hack. Use tools that support real math.
#!/bin/bash
clear
echo "Enter a number"
read a
s = 0
while [ $a -gt 0 ]
do
r = ` expr $a % 10 `
s = ` expr $s + $r `
a = ` expr $a / 10 `
done
echo "sum of digits is = $s"
This is my code guys .
I am getting a bunch of expr syntax errors.
I am using the bash shell.
Thanks!
Your error is caused by the spaces surrounding the = in the assignments, the following replacements should work (I prefer $() to using backticks since they're much easier to nest):
s=0
r=$(expr $a % 10)
s=$(expr $s + $r)
a=$(expr $a / 10)
For example, s = 0 (with the spaces) does not set the variable s to zero, rather it tries to run the command s with the two arguments, = and 0.
However, it's not really necessary to call the external expr1 to do mathematical manipulation and capture the output to a variable. That's because bash itself can do this well enough without resorting to output capture (see ARITHMETIC EVALUATION in the bash man page):
#!/bin/bash
clear
read -p "Enter a number: " number
((sum = 0))
while [[ $number -gt 0 ]]; do
((sum += number % 10))
((number /= 10))
done
echo "Sum of digits is $sum"
You'll notice I've made some other minor changes which I believe enhances the readability, but you could revert back to the your original code if you wish and just use the ((expression)) method rather than expr.
1 If you don't mind calling external executables, there's no need for a loop in bash, you could instead use sneakier methods:
#!/bin/bash
clear
read -p "Enter a number: " number
echo "Sum of digits is $(grep -o . <<<$number | paste -sd+ | bc)"
But, to be brutally honest, I think I prefer the readable solution :-)
iamnewbie: this code is inefficient but it should extract the substring, the problem is with last echo statement,need some insight.
function regex {
#this function gives the regular expression needed
echo -n \'
for (( i = 1 ; i <= $1 ; i++ ))
do
echo -n .
done
echo -n '\('
for (( i = 1 ; i <= $2 ; i++ ))
do
echo -n .
done
echo -n '\)'
echo -n \'
}
# regex function ends
echo "Enter the string:"
read stg
#variable stg holds the string entered
if [ -z "$stg" ] ; then
echo "Null string"
exit
else
echo "Length of the $stg is:"
z=`expr "$stg" : '.*' `
#variable z holds the length of given string
echo $z
fi
echo "Enter the number of trailing characters to be extracted from $stg:"
read n
m=`expr $z - $n `
#variable m holds an integer value which is equal to total length - length of characters to be extracted
x=$(regex $m $n)
echo ` expr "$stg" : "$x" `
#the echo statement(above) is just printing a newline!! But not the result
What I intend to do with this code is, if I enter "racecar" and give "3" , it should display "car" which are the last three characters. Instead of displaying "car" its just printing a newline. Please correct this code rather than giving a better one.
Although you didn't ask for a better solution, it's worth mentioning:
$ n=3
$ stg=racecar
$ echo "${stg: -n}"
car
Note that the space after the : in ${stg: -n} is required. Without the space, the parameter expansion is a default-value expansion rather than a substring expansion. With the space, it's a substring expansion; -n is interpreted as an arithmetic expression (which means that n is interpreted as $n) and since the result is a negative number, it specifies the number of characters from the end to start the substring. See the Bash manual for details.
Your solution is based on evaluating the equivalent of:
expr "$stg" : '......\(...\)'
with an appropriate number of dots. It's important to understand what the above bash syntax actually means. It invokes the command expr, passing it three arguments:
arg 1: the contents of the variable stg
arg 2: :
arg 3: ......\(...\)
Note that there are no quotes visible. That's because the quotes are part of bash syntax, not part of the argument values.
If the value of stg had enough characters, the result of the above expr invocation would be to print out the 7th, 8th and 9th character of the value of stg`. Otherwise, it would print a blank line, and fail.
But that's not what you are doing. You're creating the regular expression:
'......\(...\)'
which has single quotes in it. Since single-quotes are not special characters in a regex, they match themselves; in other words, that pattern will match a string which starts with a single quote, followed by nine arbitrary characters, followed by another single quote. And if the string does match, it will print the three characters prior to the second single-quote.
Of course, since the regular expression you make has a . for every character in the target string, it won't match the target even if the target started and begun with a single-quote, since there would be too many dots in the regex to match that.
If you don't put single quotes into the regex, then your program will work, but I have to say that few times have I seen such an intensely circuitous implementation of the substring function. If you're not trying to win an obfuscated bash competition (a difficult challenge since most production bash code is obfuscated by nature), I'd suggest you use normal bash features instead of trying to do everything with regexen.
One of those is the syntax to determine the length of a string:
$ stg=racecar
$ echo ${#stg}
7
(although, as shown at the beginning, you don't actually even need that.)
What about:
$ n=3
$ string="racecar"
$ [[ "$string" =~ (.{$n})$ ]]
$ echo ${BASH_REMATCH[1]}
car
This looks for the last n characters at the end of the line. In a script:
#!/bin/bash
read -p "Enter a string: " string
read -p "Enter the number of characters you want from the end: " n
[[ "$string" =~ (.{$n})$ ]]
echo "These are the last $n characters: ${BASH_REMATCH[1]}"
You may want to add some more error handling, but this'll do it.
I'm not sure you need loops for this task. I wrote some example to get two parameters from user and cut the word according to it.
#!/bin/bash
read -p "Enter some word? " -e stg
#variable stg holds the string entered
if [ -z "$stg" ] ; then
echo "Null string"
exit 1
fi
read -p "Enter some number to set word length? " -e cutNumber
# check that cutNumber is a number
if ! [ "$cutNumber" -eq "$cutNumber" ]; then
echo "Not a number!"
exit 1
fi
echo "Cut first n characters:"
echo ${stg:$cutNumber}
echo
echo "Show first n characters:"
echo ${stg:0:$cutNumber}
echo "Alternative get last n characters:"
echo -n "$stg" | tail -c $cutNumber
echo
Example:
Enter some word? TheRaceCar
Enter some number to set word length? 7
Cut first n characters:
Car
Show first n characters:
TheRace
Alternative get last n characters:
RaceCar
This is the programme I use.
clear
echo enter a string
read string
length=`echo -n $string | wc -c`
v=0
cons=0
d=0
s=0
len=$length
while [ $len -gt 0 ]
do
h=`echo $string | cut -c $len`
case $h in
[AaEeIiOoUu]) v=`expr $v + 1`
;;
[BbCcDdEeFfGgHhIiJjKkLlMmNnOoPpQqRrSsTtUuVvWwXxYyZz]) cons=`expr $cons + 1`
;;
[0-9]) d=`expr $d + 1`
;;
' ') s=`expr $s + 1 `
;;
esac
len=`expr $len - 1 `
done
spl=`expr $len - $v - $cons - $d - $s`
echo "vowels = $v"
echo "consonants = $cons"
echo "digits = $d"
echo "spaces = $s"
echo "special character = $spl"
The program counts all other types of characters except special characters. The output shows a minus value even if there are special characters in the input value. How can I modify the programme to make it display correct number of special characters in the input?
While your script is technically (almost) correct, it is not very pretty (both visually and in terms of code beauty).
The reason your version is not working as expected #devnull has already pointed out, but there is another bug that I will explain further down.
Since you are using bash you could rewrite the whole thing in a more idiomatic, readable and shorter way.
Here is a rewritten version of your script (comments and explanations follow below):
#!/bin/bash
clear
IFS= read -p "enter a string: " string
for ((i = 0; i < ${#string}; i++)); do
case "${string:$i:1}" in
[AaEeIiOoUu]) ((vowels++)) ;;
[[:alpha:]]) ((consonants++)) ;;
[[:digit:]]) ((digits++)) ;;
[[:space:]]) ((whitespace++)) ;;
*) ((others++)) ;;
esac
done
echo "vowels = ${vowels-0}"
echo "consonants = ${consonants-0}"
echo "digits = ${digits-0}"
echo "whitespace = ${whitespace-0}"
echo "other characters = ${others-0}"
indentation
First off, you should always indent your code blocks (if constructs, loops, switch (case) statements, ...) for readability, e.g.
while [ $len -gt 0 ]; do
do_stuff
done
read, whitespace and the prompt
Since you are using bash, read is capable of displaying a prompt for you - the extra echo is not needed. Furthermore, read strips off leading and trailing whitespace which results in an incorrect calculation unless you set IFS to the null string:
IFS= read -p "this is my prompt: " string
iterating over characters in a string
You can use parameter expansion to both get the length of the string as well as slice out one character at a time using a for loop, getting rid of the unnecessary cut and avoiding a subshell.
# ${#string} = length of $string
for ((i = 0; i < ${#string}; i++)); do
c=${string:$i:1} # c is set to the character at position $i in string $string
done
character classes
First off, your consonants statement still includes Ii for the match. Technically it doesn't matter since you cannot fall-through from the vowels match, but if this is an assignment you probably want to remove it.
That being said, you could just use the short character class for readability:
[AaEeIiOoUu]) vowel_stuff ;;
[a-zA-Z]) consonant_stuff ;; # vowels already matched above, so superfluous characters don't matter here
To make your life even easier, there are so called character classes which you can use instead, e.g.
[:digit:] = [0-9]
[:space:] = tabs, newline, form feed, carriage return, space
etc.
Note that your current locale influences certain character classes.
the special characters case
Just use the default case in the switch statement to count them, then you can skip calculating those afterwards:
case ... in
vowels) handle_vowel ;;
[...]
*) handle_other_character ;;
esac
defaults
Using parameter expansion you can also get rid of initializing your variables with 0, you can on-the-fly expand the variables to 0 if they are not set, i.e. they have not been incremented during the loop.
backticks
Unless you are writing code that has to be super-portable and has to work in all kinds of old shells, use the $() syntax in stead of ``.
arithmetic expressions
Same as above, unless you really need it you can use (( )) for arithmetic expressions, e.g.
a=5
((a = a + 10)) # or even ((a += 10))
# $a is now 15
Google and the Advanced Bash-Scripting Guide as well as the bash sections of Greg's Wiki are your friends.
You are using the wrong variable while calculating spl. Using len is clearly wrong as you're decrementing it within the loop and it becomes 0 when the loop is done.
Instead of saying:
spl=`expr $len - $v - $cons - $d - $s`
say:
spl=`expr $length - $v - $cons - $d - $s`