Skip to content
Lesson 3 of 14

Step 1 of 5 · Reading · ~3 min

Learn

Getting Started

Arithmetic

By the end of this lesson you can do integer math in bash, split a quantity into whole units and a remainder with / and %, and avoid the traps that come from bash treating everything as text.

Variables hold text, so 2 + 2 is three words, not math. Arithmetic only happens inside a dedicated context. The one you will use most is the arithmetic expansion $(( )):

apples=7
crates=3
echo "$((apples * crates))"          # 21
echo "$(( (apples + 1) * crates ))"  # 24

Everything between the double parentheses is evaluated as integer math, and the result is substituted into the line as text. Inside, variables need no $, spaces are free, and the operators are the ones from C: + - * / %, ** for powers, comparisons that give 1 or 0, and the usual precedence (* and / before + and -). When in doubt, add parentheses.

Integer only

echo "$((7 / 2))"       # 3     the fraction is dropped
echo "$((-7 / 2))"      # -3    truncated toward zero, not floored
echo "$((-7 % 3))"      # -1    the remainder takes the sign of the left side
echo "$((3.5 + 1))"     # error: bash has no floating point at all

When a script needs decimals, it delegates to a program, which is what bash is for: echo "3.5 + 1.25" | bc -l prints 4.75.

Splitting a quantity with / and %

Division and remainder are a pair. For positive n and d, n / d is how many whole ds fit in n, and n % d is what is left over. Together they split one number into units:

cents=1234
dollars=$((cents / 100))        # 12
left=$((cents % 100))           # 34
quarters=$((left / 25))         # 1
pennies=$((left % 25))          # 9
echo "$dollars dollars, $quarters quarters, $pennies pennies"

Each step works on the remainder of the step before. A useful check is that the pieces add back up: 100 * 12 + 34 is 1234.

Statement forms

count=$((count + 1))      # assignment through an expansion
((count++))               # the same thing as a command
(( total += 5 ))          # compound assignment, only inside (( ))

Outside an arithmetic context total+=5 is string append: with total=10 it makes total the text 105. That is a quiet and nasty bug, so do your math inside (( )) or $(( )).

Where numbers come from

In real scripts numbers arrive as text from read or from other programs. Inside $(( )) text that looks numeric is converted automatically:

read -r n          # n holds the text 42
echo "$((n * 2))"  # 84

One sharp edge: a number with a leading zero is octal. $((010)) is 8, and $((08)) is an error because 8 is not an octal digit. Force decimal with 10#: $((10#$n)). Non-numeric text in an arithmetic context is treated as a variable name, which usually means 0, silently.

You will also see expr 7 + 2 in old scripts. Recognise it, and never write it.

Common mistakes

  • No expansion. echo "n * 2" prints the characters n * 2. Without $(( )) there is no math.
  • Bare $((...)) as a command. $((7 * 3)) on a line by itself makes bash try to run a program named 21.
  • Expecting decimals. $((7 / 2)) is 3, never 3.5.
  • Reading remainders wrongly. The remainder is always smaller than the divisor, so a minutes value of 75 means a division is missing.

Your exercise

Convert a number of seconds into hours, minutes and seconds. Each unit is carved out of what the larger unit left behind, as in the coin example, but with three levels. The graded edge cases are zeros in the middle (3600) and values just below a boundary (3599).

Up nextStringsStrings and Output

Discussion

Ask a question, share an insight, or help someone who’s stuck.

Sign in to post a comment or reply.

Loading…