Bash Operations and Operators

When writing shell scripts, performing mathematical calculations and logical evaluations is a fundamental requirement. However, one of the most important things to understand about Bash is its native limitation: Bash only supports integer arithmetic. Unlike higher-level languages like Python or JavaScript, Bash cannot natively handle decimal points or floating-point numbers.

To navigate this, Bash provides several built-in mechanisms for integer math, while relying on external utilities like bc or awk for more complex floating-point calculations. This guide explores the various ways to perform arithmetic, the different types of operators available, and the best practices for writing robust, error-free scripts.

Methods of Arithmetic Calculation

There are several ways to perform calculations in Bash, ranging from built-in syntax to external commands.

1. Arithmetic Expansion: $((...))

This is the most modern and widely used method. Arithmetic expansion allows you to evaluate an expression and return the result. A significant advantage of this syntax is that you do not need to use the $ prefix for variables inside the double parentheses.

a=10
b=3
echo $((a + b))   # Output: 13
echo $((a * b))   # Output: 30
echo $((a / b))   # Output: 3 (Note: This is integer division)
echo $((a % b))   # Output: 1 (Remainder)
echo $((a ** b))  # Output: 1000 (Exponentiation)

2. The let Command

The let command is a built-in that allows you to perform arithmetic operations and assign the result directly to a variable.

let "count = 5 + 2"
echo $count  # Output: 7

3. The expr Command

expr is an external utility. While it is older and less efficient than arithmetic expansion, you may encounter it in legacy scripts. It has strict syntax requirements: operators must be surrounded by spaces, and certain characters like * must be escaped with a backslash to prevent the shell from interpreting them as wildcards.

expr 10 + 5    # Output: 15
expr 10 \* 5   # Output: 50 (Note the escaped asterisk)

Arithmetic Operators

Bash provides a comprehensive set of operators for manipulating integers.

Standard Operators

  • + : Addition
  • - : Subtraction
  • * : Multiplication
  • / : Division (Integer division; the fractional part is discarded)
  • % : Modulo (Returns the remainder)
  • ** : Exponentiation (Power)

Increment and Decrement

You can use double parentheses ((...)) to perform increment or decrement operations, which is particularly useful in for loops or counters.

i=1
((i++))  # Increment i by 1
echo $i  # Output: 2

((i--))  # Decrement i by 1
echo $i  # Output: 1

Compound Assignment

To make your code more concise, Bash supports compound assignment operators that perform an operation and assign the result back to the variable in one step.

  • += : Add and assign
  • -= : Subtract and assign
  • *= : Multiply and assign
  • /= : Divide and assign
  • %= : Modulo and assign

Comparison Operators

Comparison is essential for controlling the flow of your script via if statements and loops. Bash distinguishes between integer comparisons and string comparisons.

Integer Comparison

When comparing numbers, you must use specific flags rather than mathematical symbols like > or <.

  • -eq : Equal to
  • -ne : Not equal to
  • -gt : Greater than
  • -lt : Less than
  • -ge : Greater than or equal to
  • -le : Less than or equal to
if [ "$a" -gt "$b" ]; then
  echo "$a is greater than $b"
fi

String Comparison

For comparing text, standard symbols are used.

  • = or == : Equal to
  • != : Not equal to
  • -z : String is empty (zero length)
  • -n : String is not empty
str1="hello"
str2="world"

if [[ "$str1" != "$str2" ]]; then
  echo "The strings are different."
fi

if [[ -z "$str1" ]]; then
  echo "The string is empty."
fi

Pro Tip: Inside the [[ ]] construct, you can use > and < to perform lexicographical (dictionary order) comparisons.


Logical Operators

Logical operators allow you to combine multiple conditions.

  • && : Logical AND
  • || : Logical OR
  • ! : Logical NOT

While the older [ ] (test) command uses -a (AND) and -o (OR), it is highly recommended to use the modern [[ ]] keyword with && and ||. The [[ ]] syntax is more powerful, handles empty variables more gracefully, and supports pattern matching.

# Checking if both conditions are true
if [[ $a -gt 0 && $b -gt 0 ]]; then
  echo "Both numbers are positive."
fi

File Test Operators

One of Bash's greatest strengths is its ability to interact with the filesystem. You can use specific operators to check the properties of files and directories.

  • -e : True if the file/directory exists.
  • -f : True if it is a regular file.
  • -d : True if it is a directory.
  • -r : True if the file is readable.
  • -w : True if the file is writable.
  • -x : True if the file is executable.
  • -s : True if the file exists and has a size greater than zero.
  • -L : True if the file is a symbolic link.
if [ -f "/etc/passwd" ]; then
  echo "The file exists and is a regular file."
fi

if [ -d "/tmp" ]; then
  echo "/tmp is a directory."
fi

Bitwise Operators

For low-level programming or bit manipulation, Bash supports bitwise operators within the $(( )) or (( )) contexts.

  • & : Bitwise AND
  • | : Bitwise OR
  • ^ : Bitwise XOR
  • ~ : Bitwise NOT (Complement)
  • << : Left shift
  • >> : Right shift
echo $((5 & 3))   # Output: 1
echo $((5 | 3))   # Output: 7
echo $((5 << 1))  # Output: 10

Handling Floating-Point Arithmetic

Since Bash cannot handle decimals, you must call external tools when precision is required.

Using bc (Basic Calculator)

bc is a command-line calculator that supports arbitrary-precision arithmetic. You can use the scale variable to define how many decimal places you want in your result.

# Calculate 10 / 3 with 2 decimal places
echo "scale=2; 10 / 3" | bc   # Output: 3.33

Using awk

awk is a powerful text-processing language that also handles floating-point math exceptionally well. It is often faster than bc for simple calculations within a pipeline.

awk 'BEGIN {printf "%.2f\n", 10/3}'  # Output: 3.33

Best Practices and Summary

To write clean and professional Bash scripts, keep these rules in mind:

  1. Prefer [[ ]] over [ ]: The double-bracket syntax is a Bash keyword that is safer, supports regex, and avoids many common pitfalls regarding word splitting and globbing.
  2. Mind the Spaces: When using the [ ] command, you must have spaces around the brackets and the operators (e.g., [ "$a" = "$b" ]).
  3. Simplify Arithmetic: Inside $(( )), you don't need the $ sign for variables. Writing $((a + b)) is cleaner than $(( $a + $b )).
  4. Integer Awareness: Always remember that / performs integer division. If you need a decimal result, switch to bc or awk.
  5. Avoid Overflow: Bash uses signed long integers. While the range is large, be cautious when performing extremely large multiplications.

Comprehensive Example

The following script demonstrates a practical implementation of these concepts:

#!/bin/bash

read -p "Enter two integers: " x y

# Basic Arithmetic
sum=$((x + y))
diff=$((x - y))
prod=$((x * y))

# Conditional Logic for Division
if [ "$y" -ne 0 ]; then
  quot=$((x / y))
  rem=$((x % y))
else
  quot="Undefined (Division by zero)"
  rem="Undefined"
fi

echo "--- Results ---"
echo "Sum: $sum"
echo "Difference: $diff"
echo "Product: $prod"
echo "Quotient: $quot"
echo "Remainder: $rem"

# File Test Example
if [[ -f "$0" ]]; then
  echo "Note: This script is running as a regular file."
fi