Bash from the ground up

Arithmetic in the shell

Working with numbers: the double round brackets, the everyday operators, and the one rule that surprises everybody — the shell counts in whole numbers only.

Part 9 of 12

Advanced15 min readBack to Courses

Numbers are written between two pairs of brackets

$((7 + 2)) is the shell asking a piece of arithmetic for its answer. Between the brackets you can use +, - for subtraction, a star for multiplication, / for division, % for the remainder, and ** to raise a number to a power. Inside those brackets the names of variables may be written without their dollar signs, which is why $((count + 1)) and $(( $count + 1 )) both work. The brackets are an arithmetic expansion, not a function call.

When the answer is assigned back to a name, the brackets are all you need: count=$((count + 1)). That line is the counter idiom from the loops chapter, and ((count++)) is the same thing written shorter. The short form is convenient, but it hides the read and the write in one token.

Whole numbers only

The shell counts in whole numbers, and it does not warn you when a division loses its fraction: seven divided by two is three here, and the half is simply gone. That is a deliberate limitation rather than a bug, and it is why money usually travels through a script in whole units — cents, not pounds. The fraction is discarded, not rounded, so 7 divided by 2 is 3 and 9 divided by 2 is 4.

A comparison inside the brackets gives 1 for true and 0 for false, so arithmetic can be used as a test when the question is a numeric one. When you truly need decimals, the shell is the wrong tool and awk is the right one; the chapter on text tools shows that side by side with this one. A comparison in arithmetic is still a number: 1 for true and 0 for false.

The operators, and where the fraction goes

a=7
b=2

echo "$((a + b))"     # 9
echo "$((a - b))"     # 5
echo "$((a * b))"     # 14
echo "$((a / b))"     # 3, not 3.5
echo "$((a % b))"     # 1, the remainder
echo "$((2 ** 10))"   # 1024

# A counter: the name is a number again when assigned.
count=0
count=$((count + 5))
echo "count is $count"

# Comparisons answer with 1 or 0.
echo "$((a > b))"     # 1
echo "$((a < b))"     # 0

Bases, limits and the octal trap

Arithmetic is done on signed 64-bit integers. That is large enough for counters, prices in cents and most everyday sums, but it wraps around if you push it far enough. There is no decimal type and no overflow warning. The wrap-around is silent, so a counter near the limit is worth checking.

A number with a leading zero is read in octal. $(( 010 )) is 8, and $(( 08 )) is an error rather than 8, which is why a value coming from a text field should be normalised with 10# — $(( 10#08 )) is 8. Bases can also be written explicitly: 16#ff is 255 and 2#1010 is 10. 10# forces base ten, which is what a padded field usually means.

Arithmetic as a test

(( expression )) is a command whose exit status is zero when the expression is non-zero. That is what lets if (( count > 0 )); then work without brackets or dollar signs, and it is the clearest way to write a numeric condition. The status is the answer, so the arithmetic command can sit directly after if.

The same brackets are a shorthand for assignment: (( total += 5 )) changes the name in place. But (( count++ )) returns the status of the value before the increment, so when count was zero the command reports failure — a trap under set -e. Prefer count=$((count + 1)) when the status matters. The old value is what the post-increment returns, even though the name has moved on.

Where the numbers come from

The arithmetic brackets read names as numbers, so a value that is not a number becomes zero. count=three followed by $((count + 1)) prints 1, not an error, because the shell reads the name and finds no digits. That silence is why a value from a file should be checked before it reaches the arithmetic.

A leading zero is the other trap: 010 is eight, and 08 is not a valid octal number at all. Text from a date field or a padded counter often carries that zero. Prefixing the value with 10# tells the shell to read it in base ten, which is what the person who wrote the field meant.

Counters, remainders and powers

Run it and check each line against the arithmetic you would do on paper. Then add a line that adds 10% to a number by multiplying before dividing, and one that prints a remainder.

Output
Click "Run" to execute your code

Because the shell drops the fraction, $(( 1 / 3 )) is 0 and not something like nought point three. Multiply first, then divide — $(( 100 / 3 )) is 33 — if the answer needs to keep its meaning.

Three arithmetic traps: a leading zero turns 08 into an invalid octal number, division drops the fraction without a word, and (( count++ )) answers with the old value, which can look like a failed command. Use 10# for text that might start with a zero, multiply before dividing, and prefer the explicit assignment when status matters.

Try these next

  • Convert 16#ff, 2#1010 and 10#08 to decimal and print each result.
  • Compute a 15 percent tip on 4,250 cents by multiplying before dividing.
  • Use if (( ... )) to print whether a number is even, with % 2.

Money in cents, printed properly

Run it and check the total against the arithmetic on paper. Then change the price and the quantity, and add a line that prints the discount as a percentage.

Output
Click "Run" to execute your code

Check what you learned

One question at a time. A wrong answer explains itself — take another go.

Part 1 of 5

What does echo $(( 7 / 2 )) print?