Skip to main content
Bash beginner Lesson 5 of 22

Bash Operators

Learn arithmetic operators with $(( )), floating-point math with bc, string comparison, and the difference between test, [ ], and [[ ]].

Arithmetic with $(( ))

Bash only supports integer arithmetic natively, but it does it well. The $(( )) construct evaluates an arithmetic expression and returns the result as a string. It is far more readable than calling expr or bc for integer operations, and it understands all the standard operators you would expect from C.

a=10
b=3

echo $(( a + b ))    # 13
echo $(( a - b ))    # 7
echo $(( a * b ))    # 30
echo $(( a / b ))    # 3  (integer division — remainder is discarded)
echo $(( a % b ))    # 1  (modulo — the remainder)
echo $(( a ** b ))   # 1000 (exponentiation)

# Increment / decrement
count=5
(( count++ ))
echo "$count"        # 6
(( count-- ))
echo "$count"        # 5
(( count += 3 ))
echo "$count"        # 8

Use (( )) (without the leading $) for side effects like incrementing counters in loops. Use $(( )) when you need to capture the result into a variable or echo it.

# Side effect only — no $ needed, used in loops and conditionals
(( total += price ))

# Capture the value into a variable
area=$(( width * height ))
echo "Area: $area"

Bitwise operators work too, which is useful for flag manipulation or low-level data processing:

echo $(( 6 & 3 ))    # 2  (AND)
echo $(( 6 | 3 ))    # 7  (OR)
echo $(( 6 ^ 3 ))    # 5  (XOR)
echo $(( ~6 ))       # -7 (NOT)
echo $(( 1 << 3 ))   # 8  (left shift — multiply by 2^3)
echo $(( 16 >> 2 ))  # 4  (right shift — divide by 2^2)

let

let is an older alternative to (( )). Avoid it in new scripts — (( )) is cleaner and more consistent with modern bash style.

let "a = 5 + 3"
echo "$a"    # 8

let a++
echo "$a"    # 9

# Equivalent with (( )) — prefer this
(( a = 5 + 3 ))
(( a++ ))

Floating Point with bc

Bash’s $(( )) only handles integers, so any calculation involving decimals requires an external tool. bc is the standard choice — it is a full-featured arbitrary-precision calculator that reads expressions from stdin.

# Basic float arithmetic
echo "3.14 * 2" | bc           # 6.28
echo "scale=4; 22/7" | bc      # 3.1428  (scale sets the number of decimal places)

# In a script — compute pi using bc's math library
PI=$(echo "scale=10; 4*a(1)" | bc -l)   # -l loads the math library (includes atan)
echo "$PI"   # 3.1415926535

# Practical: calculate a percentage
total=250
done=175
pct=$(echo "scale=1; $done * 100 / $total" | bc)
echo "Progress: ${pct}%"   # Progress: 70.0%

Floating Point with awk

awk is often more convenient than bc for float math in scripts because it handles printf formatting natively and does not need a pipe setup for simple expressions.

awk 'BEGIN { printf "%.2f\n", 22/7 }'         # 3.14
awk 'BEGIN { printf "%.4f\n", sqrt(2) }'       # 1.4142

# With shell variables — pass them in with -v or via string interpolation
width=10.5
height=4.2
area=$(awk "BEGIN { printf \"%.2f\", $width * $height }")
echo "Area: $area"   # Area: 44.10

Comparison Operators

Numeric Comparisons

Numeric comparisons use letter-based flags like -eq and -lt inside [ ] and [[ ]], or the familiar == and < inside (( )). The two syntaxes serve different contexts — use [[ ]] for conditions in if statements and (( )) for arithmetic conditionals.

a=10
b=20

# Inside [ ] or [[ ]] — use -eq, -ne, -lt, -gt, -le, -ge
[[ $a -eq $b ]] && echo "equal"
[[ $a -ne $b ]] && echo "not equal"       # prints
[[ $a -lt $b ]] && echo "a less than b"   # prints
[[ $a -gt $b ]] && echo "a greater"
[[ $a -le $b ]] && echo "a <= b"          # prints
[[ $a -ge $b ]] && echo "a >= b"

# Inside (( )) — use standard C-style operators
(( a == b )) && echo "equal"
(( a != b )) && echo "not equal"          # prints
(( a < b ))  && echo "a less"             # prints

String Comparisons

String comparisons use ==, !=, <, and > inside [[ ]]. The -z and -n tests check emptiness, which is essential for validating that required variables or arguments were actually provided.

s1="apple"
s2="banana"

[[ "$s1" == "$s2" ]]  && echo "equal"
[[ "$s1" != "$s2" ]]  && echo "not equal"    # prints
[[ "$s1" < "$s2" ]]   && echo "s1 before s2" # lexicographic order, prints
[[ "$s1" > "$s2" ]]   && echo "s1 after s2"

# Check if string is empty or non-empty
name=""
[[ -z "$name" ]] && echo "empty"          # prints (-z = zero length)
[[ -n "$name" ]] && echo "non-empty"

name="Alice"
[[ -z "$name" ]] && echo "empty"
[[ -n "$name" ]] && echo "non-empty"      # prints (-n = non-zero length)

Pattern Matching in [[ ]]

One of the most useful features of [[ ]] over [ ] is the ability to match glob patterns and regular expressions directly in conditions, without calling grep or expr.

filename="report_2025.csv"

# Glob pattern matching (not regex — use * and ? as wildcards)
[[ "$filename" == *.csv ]]    && echo "is a CSV"     # prints
[[ "$filename" == report_* ]] && echo "is a report"  # prints

# Regex matching with =~ (uses POSIX Extended Regular Expressions)
[[ "$filename" =~ ^report_[0-9]{4}\.csv$ ]] && echo "matches pattern"  # prints

test, [ ], and [[ ]]

All three evaluate conditions, but they are not equivalent. [[ ]] is the right choice for bash scripts; [ ] is for POSIX-portable scripts that must run on any shell.

# test — a standalone command, POSIX portable
test -f /etc/passwd && echo "file exists"

# [ ] — synonym for test, POSIX portable
[ -f /etc/passwd ] && echo "file exists"

# [[ ]] — bash built-in, safer and more powerful
[[ -f /etc/passwd ]] && echo "file exists"

Key differences:

Feature[ ][[ ]]
POSIX portableYesNo (bash only)
Word splitting on unquoted varsYes (dangerous)No
Pattern matching ==NoYes
Regex =~NoYes
&& and || insideNo (use -a, -o)Yes
Requires quoting varsYesUsually not needed

Prefer [[ ]] in all bash scripts. Use [ ] only when writing POSIX-portable scripts (#!/bin/sh).

Logical Operators

Logical operators let you combine conditions. Inside [[ ]], use && and || directly. Outside conditions, they control whether the next command runs based on the previous command’s exit code.

age=25
role="admin"

# AND — both conditions must be true
if [[ $age -gt 18 && "$role" == "admin" ]]; then
  echo "Adult admin"
fi

# OR — at least one condition must be true
if [[ "$role" == "admin" || "$role" == "superuser" ]]; then
  echo "Privileged user"
fi

# NOT — condition must be false
if [[ ! -f "/tmp/lockfile" ]]; then
  echo "No lock file — safe to proceed"
fi

Short-circuit operators outside of conditions control command execution flow:

mkdir /tmp/work && cd /tmp/work     # run second only if first succeeds
rm file.txt || echo "file not found" # run second only if first fails

File Test Operators

File test operators let you check properties of files and paths before acting on them. Using them as guards prevents cryptic error messages when expected files are missing or have the wrong permissions.

[[ -e path ]]   # exists (file or directory or symlink)
[[ -f path ]]   # regular file (not a directory or symlink)
[[ -d path ]]   # directory
[[ -r path ]]   # readable by the current user
[[ -w path ]]   # writable by the current user
[[ -x path ]]   # executable by the current user
[[ -s path ]]   # exists and is non-empty (size > 0)
[[ -L path ]]   # symbolic link
[[ -z "$var" ]] # string is empty
[[ -n "$var" ]] # string is non-empty

A practical guard pattern for validating a config file at the start of a script:

config="/etc/myapp/config.yml"

if [[ ! -f "$config" ]]; then
  echo "ERROR: config file not found: $config" >&2
  exit 1
fi

if [[ ! -r "$config" ]]; then
  echo "ERROR: config file not readable: $config" >&2
  exit 1
fi

What’s Next

With operators in hand, the next tutorial covers control flow — if/elif/else, case statements, and loops.

Frequently Asked Questions

What is the difference between [ ] and [[ ]]?
[[ ]] is a Bash built-in that is safer and more powerful. It supports pattern matching with ==, regex with =~, and doesn't require quoting variables to avoid word splitting. [ ] is POSIX and available in all shells but has more gotchas.
How do I do floating point math in Bash?
Use the bc utility: result=$(echo '3.14 * 2' | bc) or awk: result=$(awk 'BEGIN {print 3.14 * 2}'). Bash's $(( )) only handles integers.
Why does -eq work for numbers but not strings?
-eq is a numeric comparison operator. Use = or == for string comparisons inside [ ] or [[ ]].