How to Create a Bash Calculator

How to Create a Bash Calculator

How to Create a Bash Calculator

Bash gets a bad reputation for math, and honestly, it’s a little deserved — the shell’s built-in arithmetic is integer-only, and anything involving decimals requires reaching for an external tool. Once I understood exactly where Bash’s native arithmetic ends and where tools like bc and awk pick up the slack, building a genuinely useful calculator script became straightforward. This article walks through building one from a simple integer calculator up to a full interactive tool that handles decimals, and covers the internals of how Bash arithmetic actually works.

Bash’s Native Arithmetic

Bash supports integer arithmetic natively through the $(( )) syntax:

result=$((5 + 3))
echo "$result"

Output:

8

It supports the standard operators: +, -, *, /, % (modulo), and ** (exponentiation):

echo $((10 / 3))    # 3 (integer division, truncates)
echo $((10 % 3))    # 1 (remainder)
echo $((2 ** 8))    # 256

Notice that 10 / 3 returns 3, not 3.333... — Bash arithmetic is strictly integer-based and truncates any fractional result. This is the single biggest limitation you’ll hit building a calculator in pure Bash.

The let and expr Alternatives

Two older ways to do the same arithmetic:

let result=5+3
echo "$result"

result=$(expr 5 + 3)
echo "$result"

I rarely use either in new scripts — $(( )) is cleaner, safer, and is the modern standard. expr in particular requires spaces around every operator and is slower since it’s an external command rather than a shell built-in.

Step One: A Simple Integer Calculator

#!/usr/bin/env bash
set -euo pipefail

num1=$1
operator=$2
num2=$3

case "$operator" in
    +) echo $((num1 + num2)) ;;
    -) echo $((num1 - num2)) ;;
    \*) echo $((num1 * num2)) ;;
    /) 
        if [ "$num2" -eq 0 ]; then
            echo "Error: division by zero" >&2
            exit 1
        fi
        echo $((num1 / num2))
        ;;
    %) echo $((num1 % num2)) ;;
    *) echo "Unknown operator: $operator" >&2; exit 1 ;;
esac

Run it:

./calc.sh 10 + 5
./calc.sh 10 / 3

Output:

15
3

Note the \* in the case pattern — since * is a glob wildcard in case patterns, it needs to be escaped to be matched literally as the multiplication operator, otherwise it would match everything and always trigger that branch.

Step Two: Adding Decimal Support with bc

To handle real decimal math, I bring in bc, the classic Unix arbitrary-precision calculator:

echo "10 / 3" | bc -l

Output:

3.33333333333333333333

The -l flag loads the standard math library, which also enables functions like sqrt(), s() (sine), c() (cosine), and sets a reasonable default decimal precision (scale).

Step-by-Step: A Full Decimal-Capable Calculator Script

#!/usr/bin/env bash
set -euo pipefail

usage() {
    echo "Usage: $0 <num1> <operator> <num2>"
    echo "Operators: + - * / % ^"
    exit 1
}

if [ "$#" -ne 3 ]; then
    usage
fi

num1=$1
operator=$2
num2=$3

if ! [[ "$num1" =~ ^-?[0-9]+(\.[0-9]+)?$ ]] || ! [[ "$num2" =~ ^-?[0-9]+(\.[0-9]+)?$ ]]; then
    echo "Error: both arguments must be numbers" >&2
    exit 1
fi

case "$operator" in
    +|-|\*|/|\^)
        result=$(echo "scale=6; $num1 $operator $num2" | bc -l)
        ;;
    %)
        result=$(echo "$num1 % $num2" | bc -l)
        ;;
    *)
        echo "Error: unknown operator '$operator'" >&2
        usage
        ;;
esac

echo "Result: $result"

Run it:

./calc.sh 10 / 3
./calc.sh 2.5 \* 4
./calc.sh 2 ^ 10

Output:

Result: 3.333333
Result: 10.0
Result: 1024

Note that in bc, ^ means exponentiation (not XOR like in some other languages), which is different from Bash’s own ** operator.

How This Works Internally

Building an Interactive Calculator Loop

For a REPL-style calculator I can keep typing into:

#!/usr/bin/env bash
set -euo pipefail

echo "Bash Calculator (type 'exit' to quit)"

while true; do
    read -rp "> " expression
    if [ "$expression" = "exit" ]; then
        break
    fi
    if [ -z "$expression" ]; then
        continue
    fi
    result=$(echo "scale=6; $expression" | bc -l 2>/dev/null) || {
        echo "Invalid expression"
        continue
    }
    echo "$result"
done

Example session:

Bash Calculator (type 'exit' to quit)
> 5 + 3
8
> 10 / 3
3.333333
> sqrt(2)
1.414213
> exit

Because it’s just feeding raw input straight into bc, this version even supports full expressions with parentheses and functions, not just single operations — bc handles the actual parsing and precedence.

Real-World Use Case: A Percentage Calculator Function

#!/usr/bin/env bash

percentage_of() {
    local part=$1
    local whole=$2
    echo "scale=2; ($part / $whole) * 100" | bc -l
}

echo "$(percentage_of 45 200)%"

Output:

22.50%

I use small functions like this inside larger reporting scripts — for example, calculating disk usage percentage or test pass rates without needing a separate calculator tool.

Automation Example: Calculating Average from a List of Numbers

#!/usr/bin/env bash
set -euo pipefail

numbers=(23 45 12 67 34 89 21)
sum=0

for n in "${numbers[@]}"; do
    sum=$((sum + n))
done

count=${#numbers[@]}
average=$(echo "scale=2; $sum / $count" | bc -l)

echo "Sum: $sum"
echo "Count: $count"
echo "Average: $average"

Output:

Sum: 291
Count: 7
Average: 41.57

This pattern — accumulate with fast native Bash integer arithmetic, then hand off to bc only for the final division — is a good balance of performance and correctness.

Alternative: Using awk for Math

awk is another solid option for decimal math, and it’s often already being used elsewhere in a script for text processing, which can save you from bringing in bc as well:

awk "BEGIN { print 10 / 3 }"

Output:

3.33333

I tend to reach for bc when I need arbitrary precision or math functions, and awk when I’m already parsing text and just need a quick calculation inline without a separate pipe.

Security Considerations

Optimization Tips

Troubleshooting

Common Mistakes to Avoid

FAQs

Why does 10 / 3 give 3 instead of 3.33 in Bash? Because Bash’s built-in arithmetic ($(( ))) only supports integers; for decimal results, pipe the expression into bc -l or use awk.

Is bc installed by default on Linux? Not always — many minimal server images and containers don’t include it, so it’s worth adding an install check or documenting it as a dependency.

Can I do trigonometry or logarithms in a Bash calculator? Yes, bc -l provides s() (sine), c() (cosine), a() (arctangent), l() (natural log), and e() (exponential) as part of its math library.

What’s the fastest option for simple integer-only math in a loop? Native Bash arithmetic ($(( ))) is fastest since it requires no external process; only reach for bc or awk once decimals or advanced functions are needed.

Summary

Building a calculator in Bash really comes down to knowing the boundary between what the shell can do natively (fast, simple integer math) and where you need to hand things off to bc or awk for decimals and advanced functions. With careful input validation and a clear case-based structure instead of eval, it’s entirely possible to build a calculator that’s both safe and genuinely useful for everyday scripting and even interactive use.

References

Exit mobile version