Writing a script to calculate disk usage percentages and send an email alert when a threshold is reached might sound straightforward at first. However, after writing half the script, trying something as simple as result=$a/$b can leave you staring at bash wondering what just happened. No error message. No useful output. Just silence. That exact moment teaches you very quickly that bash does not handle arithmetic the same way most programming languages do.
That experience, along with many others, is exactly why this guide on bash script arithmetic operators exists. If you have never performed mathematical operations inside a shell script before, this guide will give you a solid foundation to build on. If you have been scripting for years but still run into strange behavior with floating-point calculations or different operator syntaxes, the later sections will help clear those up as well. Regardless of your current experience level, the goal is simple: by the end of this guide, you should be able to confidently use arithmetic inside a real bash script instead of simply memorizing a table of symbols.
Why This Catches People Off Guard:
Bash is not Python. Variables are strings by default, and arithmetic requires explicit syntax. Here is what trips most people up early on:
- Writing
result=$a+$bgives you the string "5+3", not the number 8 - Forgetting that integer division silently drops the decimal part
- Trying to use floating point with
$(( ))and getting a syntax error - Using
*for multiplication insideexprwithout escaping it
Every one of these is fixable in about ten seconds once you know what is happening.
If you are just getting started with shell scripting, the introduction to bash scripting on LinuxTeck gives you the setup context before diving into operators.
Before the Math: How Bash Thinks About Numbers
Bash treats everything as a string unless you tell it otherwise. That is the single most important thing to understand here. When you write x=5, bash stores the string "5", not the integer 5. This is not a bug. It is just how the shell works, and once you accept it, everything else clicks.
To do actual arithmetic, you need to use one of the methods that signals to bash "treat this as a number." The main ones are double parentheses $(( )) and the external expr command. Double parentheses is what you will use 90% of the time. It is cleaner, faster (no subprocess), and supports the full range of arithmetic operators. For decimal math, bc steps in where $(( )) stops.
Note:
Inside $(( )), you can reference variables with or without the dollar sign. Both $(( a + b )) and $(( $a + $b )) work. The first form is slightly cleaner and is what most seasoned scripters use.
Here is a minimal example just to confirm your environment works before getting into specific operators:
LinuxTeck.com
# Basic arithmetic test
a=10
b=3
result=$(( a + b ))
echo "Result: $result"
Bash Script Arithmetic Operators: What They Actually Do
Bash supports eleven arithmetic operators. You will use the first six constantly. The compound assignment ones come up a lot in loops and counters. Here is each one with a working example, not just a table of symbols.
Addition (+) and subtraction (-) behave exactly as expected. No surprises there.
LinuxTeck.com
x=20
y=7
echo "Addition: $(( x + y ))"
echo "Subtraction: $(( x - y ))"
echo "Multiply: $(( x * y ))"
echo "Division: $(( x / y ))"
echo "Modulo: $(( x % y ))"
echo "Power: $(( x ** 2 ))"
Subtraction: 13
Multiply: 140
Division: 2
Modulo: 6
Power: 400
Notice that division gives you 2, not 2.857. That is integer division. Bash drops the decimal completely and gives you only the whole number. The modulo line is a separate operation: 20 % 7 asks "what is left over after dividing 20 by 7?" The answer is 6, because 7 goes into 20 twice (14), leaving 6 behind. Modulo is genuinely useful in scripts, especially for running a task every N iterations inside a loop, or checking whether a number is even or odd.
Common Mistake:
Bash does not protect against division by zero. Running echo $(( 10 / 0 )) causes a fatal arithmetic error: bash: 10 / 0: division by 0 (error token is "0"). In any script that accepts user input or calculates a divisor dynamically, always validate the divisor is not zero before performing the division.
Compound Assignment Operators: The Loop-Friendly Shorthand
These come up constantly once you start writing loops. Instead of writing x=$(( x + 1 )) every time you want to increment a counter, you can use (( x++ )) or (( x += 1 )). Same result, less typing, easier to read at a glance.
Here is a quick reference of all the compound operators working sequentially. Notice the values chain from one operation to the next, each step changing the running value. This is different from the standalone examples in Section #02 where each line used fresh variables.
LinuxTeck.com
val=20
echo "Start: $val"
(( val += 5 )) # add 5
echo "After +=5: $val"
(( val -= 3 )) # subtract 3
echo "After -=3: $val"
(( val *= 2 )) # multiply by 2
echo "After *=2: $val"
(( val /= 4 )) # divide by 4
echo "After /=4: $val"
(( val %= 3 )) # remainder
echo "After %=3: $val"
(( val++ ))
echo "After ++: $val"
(( val-- ))
echo "After --: $val"
After +=5: 25
After -=3: 22
After *=2: 44
After /=4: 11
After %=3: 2
After ++: 3
After --: 2
Three Ways to Do Arithmetic in Bash (and When to Use Each)
Double parentheses is not the only option. Depending on what you are doing and what environment you are in, you might reach for expr or a tool like bc for decimal math. Here is a real comparison of the three approaches so you can choose without guessing.
Method 1: $(( )) double parentheses. Use this for almost everything. No subshell, fast, clean.
LinuxTeck.com
a=15
b=4
result=$(( a * b ))
echo "Double parens result: $result"
Method 2: (( )) in a loop. The same double parentheses you already know works perfectly as a standalone statement for incrementing counters. No dollar sign, no substitution, just the expression. This is the preferred style to keep your scripts consistent.
LinuxTeck.com
count=0
for i in 1 2 3 4 5; do
(( count++ ))
done
echo "Count: $count"
Method 3: bc for decimal math. Use this when you actually need floating point results. bc is an external calculator tool. You pipe your expression into it and get decimal output back. This is the only clean way to do float math in bash.
LinuxTeck.com
# Calculate disk usage percentage with decimals
# Multiply first, then divide -- keeps bc precision correct
used=47
total=120
percent=$(echo "scale=2; ($used * 100) / $total" | bc)
echo "Disk used: $percent%"
The scale=2 part tells bc to give you two decimal places. The expression is written as ($used * 100) / $total rather than $used / $total * 100 for an important reason: bc applies the scale rule to the first division it encounters. If you divide first, you get a truncated intermediate result before the multiplication happens, and the final answer is off. Multiply first, then divide, and you always get the accurate decimal.
The Mistake That Silently Breaks Your Script
This one catches everyone at some point. You write what looks like perfectly valid arithmetic, the script runs without any error message, and the result is just wrong. Or it is the literal string you typed. Here is the exact mistake and the fix.
Common Mistake:
Using bare assignment syntax for arithmetic. Writing result=$a+$b or result=$a*$b does NOT perform math. Bash concatenates the strings and gives you something like "10+3" or "10*3". No error is thrown. The variable just holds a useless string.
Fix: wrap the expression in double parentheses. Use result=$(( a + b )) instead. The double parentheses tell bash to evaluate the expression as an integer operation, not string concatenation.
Here is what that mistake looks like side by side with the correct version, so you can spot it clearly:
LinuxTeck.com
a=10
b=3
# WRONG - this creates a string, not a number
wrong=$a+$b
echo "Wrong: $wrong"
# CORRECT
correct=$(( a + b ))
echo "Correct: $correct"
correct_mul=$(( a * b ))
echo "Correct mul: $correct_mul"
Correct: 13
Correct mul: 30
The wrong line actually runs fine and just silently stores "10+3" as a string. That is what makes it a nasty bug. You would only notice something went wrong further down the script when a conditional comparison or another calculation produces a totally unexpected result.
A Real Script That Actually Uses Arithmetic
Let's put this together into something you could drop into a cron job today. This script checks disk usage on a mounted filesystem, calculates the percentage used, and sends a warning message if it goes over a threshold you set. The kind of thing that saves you from waking up at 2am to a full disk alert.
This is the script that originally made arithmetic click for me. See how the exit codes and error handling pair with the arithmetic to make the logic actually reliable.
LinuxTeck.com
# Disk usage alert script
# Set your threshold and target mount point
THRESHOLD=80
MOUNT="/"
# Get current usage as an integer
# -P gives POSIX output (no wrapped lines)
# gsub removes the % sign inside awk directly
USAGE=$(df -P "$MOUNT" | awk 'NR==2 {gsub("%","",$5); print $5}')
echo "Current usage on $MOUNT: ${USAGE}%"
if (( USAGE >= THRESHOLD )); then
echo "WARNING: Disk at ${USAGE}% -- above ${THRESHOLD}% threshold"
exit 1
fi
REMAINING=$(( 100 - USAGE ))
echo "OK: ${REMAINING}% disk space still available"
exit 0
OK: 57% disk space still available
Notice the (( USAGE >= THRESHOLD )) syntax inside the if statement. Double parentheses work for arithmetic comparisons too, not just for computing values. This is cleaner and more readable than the string comparison equivalents. You can schedule this script with cron to run every hour and log the output. That is a genuinely useful monitoring tool in about 15 lines.
Tip:
When using arithmetic inside if conditions, double parentheses (( )) is the right tool. It returns exit code 0 (true) when the expression is non-zero, and exit code 1 (false) when it is zero. This is the opposite of how [ ] works, which trips some people up when mixing both styles in the same script. One thing worth noting for beginners: if the arithmetic result itself is 0, the condition is treated as false. So if (( 5 - 5 )) never enters the block, because 0 evaluates to false inside double parentheses.
Questions I Get Asked About This All the Time
Why does my arithmetic work in the terminal but break inside a script?
Usually the variable is set as a string in one place and you are trying to do math with it somewhere else without the $(( )) wrapper. Also check that your shebang is #!/bin/bash and not #!/bin/sh. Many POSIX sh implementations such as dash and busybox sh do not support (( )) arithmetic syntax at all. Scripts that use bash arithmetic features must explicitly use bash.
Can bash do floating point math without installing anything?
Not natively. $(( )) only does integer math and silently drops decimals. bc is commonly available on most Linux distributions, but minimal systems, containers, and cloud images may not include it by default; install it with your package manager if needed. Use echo "scale=2; 10 / 3" | bc for decimal output. If bc is not available, awk is a built-in alternative: awk 'BEGIN{printf "%.2f\n", 10/3}'.
What is the difference between (( )) and $(( ))?
(( )) evaluates an arithmetic expression and returns an exit code. You use it in if conditions and loops. $(( )) evaluates the expression AND substitutes the result as a value into your command or assignment. Use result=$(( a + b )) to store the result. Use if (( a > b )) to test a condition.
My script divides two numbers and always gets zero. What is wrong?
Integer division. When you divide a smaller number by a larger one with $(( )), the result is always 0 because bash drops the decimal. For example, $(( 3 / 10 )) gives you 0, not 0.3. Switch to bc with scale=2 set if you need the actual decimal result.
Does arithmetic work the same inside double quotes?
The $(( )) construct works inside double quotes and expands correctly. You can write echo "Result is $(( a + b ))" and bash will compute and insert the value. What does NOT work is bare math like echo "$a + $b" which just prints the string "10 + 3".
When should I use expr instead of $(( ))?
Rarely. expr is an external command, which means the shell launches a separate child process to run it. That overhead makes it slower than $(( )), which runs entirely inside the current shell with no forking at all. The main reason to use expr today is compatibility with very old POSIX systems where $(( )) may not be available. For any modern bash script on any current Linux distribution, double parentheses is the right choice. One catch with expr: you must escape the multiplication operator as \* or the shell interprets it as a glob.
Summary
Now that you have the arithmetic operators working, you can start building scripts that actually calculate things rather than just concatenate strings and hope for the best. The key habit to form is reaching for $(( )) any time a number needs to come out of an expression, and reaching for bc the moment you need a decimal. Most of the confusion around bash script arithmetic operators comes from not knowing which context needs which syntax, and hopefully that is much clearer now.
The next natural step from here is conditionals. Once your script can do math and compare results, you can build real logic. The bash if elif else guide on LinuxTeck picks up exactly where this leaves off. And if you want the full language reference for bash arithmetic, the GNU Bash manual section on arithmetic expansion is the definitive source.
Related Articles
- Bash if Statement: Complete Guide With Examples (Part 11 / 34)
- Bash Script Exit Codes and Error Handling (Part 5 of 34)
- Bash Conditional Statements Explained (Part 13 / 34)
- Linux Bash Scripting Automation in 2026
- Linux Shell Scripting Command Cheat Sheet
Learn step-by-step how to automate Linux tasks with real-world scripts and practical examples.