How can I add numbers in a Bash script?
Asked Answered
T

15

770

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
Trimetallic answered 14/6, 2011 at 19:25 Comment(1)
I think you will print a zero value even using following answer. There's a trick, e.g. process substition, to bring the final value of "inside num" to "outside num".Worriment
D
1299

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`

Dispersant answered 14/6, 2011 at 19:28 Comment(9)
In the first answer it prints me expr:non-numeric argument The second doesn't work..Trimetallic
@Nick: Did you try this while variables named num1 and num2 existed and had integer values?Sykes
Yew they are existed but the one variable is double.. is that a problem??Trimetallic
Yeah, that's a problem :) Note: the $((..)) arithmetic evaluation is executed in bash. expr is executed as a separate process, so it's going to be a lot slower. use the latter one on systems where the arithemtic evaluation isn't supported (sh!=bash)Dispersant
Since $((…)) is standardized by POSIX, it should become increasingly rare that expr is necessary.Whitsun
I am thinking your answer here for local variables unix.stackexchange.com/q/229049/16920Endorsee
Any issues with a function like this in your profile? function calc() { echo "${1}" | bc -l; } callable as calc '1 + 2' or calc '1 / 2'. originally I was going with $((...)) but it does not handle floating point.Albertalberta
expr POSIX documentation itself recommends to use $(()) instead in new scripts: pubs.opengroup.org/onlinepubs/9699919799/utilities/expr.htmlFruitarian
@KarolyHorvath I'm trying to use my integer $num (which equals 13 in this example), as computed in the first example, in a for loop for i in {$numDays..1}, but I get {13..1}: integer expression expected. Why isn't $num an integer?Sabir
L
180

Use the $(( )) arithmetic expansion.

num=$(( $num + $metab ))

See Chapter 13. Arithmetic Expansion for more information.

Lehet answered 14/6, 2011 at 19:28 Comment(6)
Strange. It does not work here when I try to compute 191.003 + 190.Sibship
@Sibship I believe this is because bash doesn't natively support floating point maths .i.e this method, nice as it is, only works with integers. Try using one of the dc or bc solutions instead.Swarts
Is mandatory use $ between the (())? Seems notCherey
@ManuelJordan No, num=$((num1 + num2)) should work just as well.Snowstorm
Using $ inside an arithmetic expansion is unnecessary, and in fact, it can lead to some bugs. See: shellcheck.net/wiki/SC2004.Anglia
Also, spaces after (( and before )) are unnecessary - unlike in conditionals (e.g. [ $somevar = 'something' ]Anglia
S
37

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.

Sykes answered 14/6, 2011 at 20:17 Comment(1)
@Sorpigal Thankssss a lot.. Can i ask you something else..how can i print at the end not the num but the num/10 ??Trimetallic
M
27

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
Mystique answered 14/6, 2011 at 22:32 Comment(0)
C
27

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;
Carolinacaroline answered 28/10, 2013 at 20:42 Comment(0)
R
20

I really like this method as well. There is less clutter:

count=$[count+1]
Rochkind answered 25/10, 2013 at 9:22 Comment(2)
Why is this working, by the way? How do we call this? I can't find docs about these.Jaclynjaco
Less clutter, but deprecated.Forcefeed
R
18
 #!/bin/bash
read X
read Y
echo "$(($X+$Y))"
Ribose answered 29/9, 2016 at 21:6 Comment(1)
Works well in macOS.Saransarangi
L
11

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.

Lenticularis answered 30/4, 2017 at 9:34 Comment(0)
S
11

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

Sande answered 26/4, 2020 at 21:18 Comment(0)
H
7

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.

Holograph answered 14/1, 2017 at 15:40 Comment(1)
If you use Bash it's not POSIX compliant since it won't work on POSIX machines without Bash.Yarrow
C
6
#!/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
Cattish answered 8/1, 2022 at 8:59 Comment(0)
E
4
#!/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
Edmonds answered 20/3, 2018 at 4:11 Comment(0)
E
0

This is my solution

5.1

git clone https://github.com/datablist/sample-csv-files.git

5.2

cd sample-csv-files
git log --pretty=format:"%h - %s" -n 3

5.3

awk -F, 'NR>=40 && NR<=70 {print $1":"$2":"$3}' files/people/people-100.csv

5.4

#!/bin/bash

if [ "$#" -ne 1 ]; then
echo "Utilizare: $0 adresa_de_email"
exit 1
fi

EMAIL=$1

FILE="files/people/people-10000.csv"

RESULT=$(grep -n "$EMAIL" "$FILE")

if [ -z "$RESULT" ]; then
echo "Adresa de email '$EMAIL' nu a fost găsită."
else
echo "Date găsite: $RESULT"
fi

1

Here is my solution:

5.1

mkdir -p ~/student/my-app
cd ~/student/my-app
git init

5.2

echo "Hello, World!" > ~/student/my-app/hello.txt
cd ~/student/my-app
git add hello.txt
git commit -m "Added hello.txt with 'Hello, World!'"

5.3

command -v cowsay >/dev/null 2>&1 || sudo apt-get install -y cowsay

5.4

echo "PID-ul procesului părinte este: $$" și "PPID-ul este: $(ps -p $$ -o ppid=)"

2

Esperanto answered 9/2 at 8:27 Comment(0)
B
-1

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"
Bandicoot answered 3/2, 2023 at 10:10 Comment(0)
N
-1

This is my solution: 6.1 tail -n 15 ~/.bash_history | base64 | sha256sum

6.2

#!/bin/bash

if [ "$#" -ne 1 ]; then
echo "Utilizare: $0 nume_utilizator"
exit 1
fi

USERNAME=$1

PASSWORD=$(openssl rand -base64 6 | tr -d 'A-Z+/=')

useradd -m $USERNAME

echo "$USERNAME:$PASSWORD" | chpasswd

echo "Utilizatorul '$USERNAME' a fost creat."
echo "Parola: $PASSWORD"

6.3

for i in {1..50}; do echo "This is mambo no. 5."; done > mambo_no_5.txt && openssl enc -aes-128-cbc -salt -in mambo_no_5.txt -out mambo_no_5.enc -k yourpassword 6.4 nano ~/.bashrc export HISTSIZE=50 source ~/.bashrc

1

Here is my solution: 6.1 #!/bin/bash

sum=0
count=0

while true; do
  echo -n "Introduceți un număr (0 sau non-număr pentru a opri): "
  read num
  # Verificăm dacă inputul este un număr
  if [[ $num =~ ^-?[0-9]+([.][0-9]+)?$ ]]; then
    if [ $num -eq 0 ]; then
      break
    fi
    sum=$(echo "$sum + $num" | bc)
    ((count++))
  else
    break
  fi
done

if [ $count -ne 0 ]; then
  average=$(echo "scale=2; $sum / $count" | bc)
  echo "Media aritmetică este: $average"
else
  echo "Nu s-au introdus numere valide."
fi

6.2 #!/bin/bash

if [ $# -eq 0 ]; then
  echo "Utilizare: $0 nume_utilizator"
  exit 1
fi

username=$1

if getent passwd "$username" > /dev/null 2>&1; then
  # Extragem UID-ul și shell-ul
  uid=$(getent passwd "$username" | cut -d: -f3)
  shell=$(getent passwd "$username" | cut -d: -f7)
  echo "Utilizator: $username, UID: $uid, Shell: $shell"
else
  echo "Eroare: Utilizatorul '$username' nu există."
fi

6.3

chmod +x nume_script.sh
sudo mv nume_script.sh /usr/local/bin

Sau cu alias

6.4

#!/bin/bash
if [ $# -eq 0 ]; then
  echo "Utilizare: $0 -u (utilizator curent) | -m (memorie liberă) | orice 
altceva (eroare)"
  exit 1
fi

case $1 in
  -u)
    echo "Utilizatorul curent este: $(whoami)"
    ;;
  -m)
    free -h | grep Mem | awk '{print "Memorie liberă: " $4}'
    ;;
  *)
    echo "Argument necunoscut. Utilizați -u pentru utilizatorul curent sau 
-m pentru memoria liberă."
    ;;
esac

2

Nimesh answered 9/2 at 8:14 Comment(1)
As it’s currently written, your answer is unclear. Please edit to add additional details that will help others understand how this addresses the question asked. You can find more information on how to write good answers in the help center.Frenchman

© 2022 - 2024 — McMap. All rights reserved.