Skip to content

Operators

Operators are the building blocks that allow you to perform operations on variables and values. In this chapter, we’ll cover arithmetic operators, comparison operators, logical operators, and special operators used in bash scripting. This knowledge is essential for writing conditional logic and performing calculations in your scripts.


┌─────────────────────────────────────────────────────────────────────┐
│ OPERATOR TYPES IN BASH │
└─────────────────────────────────────────────────────────────────────┘
┌──────────────────┬──────────────────────────────────────────────────┐
│ Type │ Description │
├──────────────────┼──────────────────────────────────────────────────┤
│ Arithmetic │ +, -, *, /, %, ++, --, etc. │
│ Comparison │ -eq, -ne, -lt, -le, -gt, -ge (numeric) │
│ │ =, ==, != (string) │
│ Logical │ &&, ||, ! │
│ Bitwise │ &, |, ^, ~, <<, >> │
│ File Tests │ -f, -d, -r, -w, -x, -e, -s, etc. │
│ String Tests │ -z, -n, ==, != │
│ Compound │ ( ), !, -a, -o │
└──────────────────┴──────────────────────────────────────────────────┘

arithmetic.sh
#!/usr/bin/env bash
# Addition
a=10
b=20
sum=$((a + b))
echo "$a + $b = $sum" # 10 + 20 = 30
# Subtraction
diff=$((b - a))
echo "$b - $a = $diff" # 20 - 10 = 10
# Multiplication
product=$((a * b))
echo "$a * $b = $product" # 10 * 20 = 200
# Division (integer)
quotient=$((b / a))
echo "$b / $a = $quotient" # 20 / 10 = 2
# Division with floating point
result=$(echo "scale=2; $b / $a" | bc)
echo "$b / $a = $result" # 20 / 10 = 2.00
# Modulo (remainder)
mod=$((b % a))
echo "$b % $a = $mod" # 20 % 10 = 0
increment.sh
#!/usr/bin/env bash
# Pre-increment
i=5
echo "Before: $i"
echo "++i = $((++i))" # i becomes 6, returns 6
echo "After: $i" # 6
# Post-increment
i=5
echo "Before: $i"
echo "i++ = $((i++))" # returns 5, i becomes 6
echo "After: $i" # 6
# Pre-decrement
i=5
echo "Before: $i"
echo "--i = $((--i))" # i becomes 4, returns 4
echo "After: $i" # 4
# Post-decrement
i=5
echo "Before: $i"
echo "i-- = $((i--))" # returns 5, i becomes 4
echo "After: $i" # 4
compound.sh
#!/usr/bin/env bash
# +=
x=10
x+=5
echo "x = $x" # 15 (concatenation for strings, addition for numbers)
# For strings
str="Hello"
str+=" World"
echo "str = $str" # Hello World
# -=
x=10
x-=3
echo "x = $x" # 7
# *=
x=5
x*=4
echo "x = $x" # 20
# /=
x=20
x/=4
echo "x = $x" # 5
# %=
x=17
x%=5
echo "x = $x" # 2
bitwise.sh
#!/usr/bin/env bash
# Bitwise AND
a=5 # 0101
b=3 # 0011
result=$((a & b))
echo "5 & 3 = $result" # 1 (0001)
# Bitwise OR
result=$((a | b))
echo "5 | 3 = $result" # 7 (0111)
# Bitwise XOR
result=$((a ^ b))
echo "5 ^ 3 = $result" # 6 (0110)
# Bitwise NOT
result=$((~a))
echo "~5 = $result" # -6
# Left shift
result=$((a << 2))
echo "5 << 2 = $result" # 20 (0101 -> 10100)
# Right shift
result=$((a >> 1))
echo "5 >> 1 = $result" # 2 (0101 -> 0010)
alternatives.sh
#!/usr/bin/env bash
# Using let (arithmetic in let/())
let "sum = 10 + 20"
let "product = 5 * 6"
echo "Sum: $sum, Product: $product"
# Using expr (requires spaces and command substitution)
result=$(expr 10 + 5)
echo "10 + 5 = $result"
# Using bc for floating point
result=$(echo "10 / 3" | bc -l)
echo "10 / 3 = $result" # 3.33333...
# With scale (precision)
result=$(echo "scale=4; 10 / 3" | bc)
echo "scale=4: 10 / 3 = $result" # 3.3333
# Complex calculations
result=$(echo "sqrt(16) + 2^3" | bc -l)
echo "sqrt(16) + 2^3 = $result" # 12.00000

┌─────────────────────────────────────────────────────────────────────┐
│ NUMERIC COMPARISON OPERATORS │
└─────────────────────────────────────────────────────────────────────┘
┌──────────────────┬──────────────────────────────────────────────────┐
│ Operator │ Description │
├──────────────────┼──────────────────────────────────────────────────┤
│ -eq │ Equal to │
│ -ne │ Not equal to │
│ -lt │ Less than │
│ -le │ Less than or equal to │
│ -gt │ Greater than │
│ -ge │ Greater than or equal to │
└──────────────────┴──────────────────────────────────────────────────┘
numeric_compare.sh
#!/usr/bin/env bash
a=10
b=20
# Test with [ ] (test command)
if [ "$a" -eq 10 ]; then
echo "$a equals 10"
fi
if [ "$a" -ne "$b" ]; then
echo "$a is not equal to $b"
fi
if [ "$a" -lt "$b" ]; then
echo "$a is less than $b"
fi
if [ "$a" -le 10 ]; then
echo "$a is less than or equal to 10"
fi
if [ "$b" -gt "$a" ]; then
echo "$b is greater than $a"
fi
if [ "$b" -ge 20 ]; then
echo "$b is greater than or equal to 20"
fi
modern_compare.sh
#!/usr/bin/env bash
a=10
b=20
# With [[ ]], you can use < and > for numeric comparison
# But these work as string comparison in [ ]
# So use (( )) for numeric comparison in bash
# Using (( )) for numeric comparison (recommended)
if (( a == 10 )); then
echo "$a equals 10"
fi
if (( a != b )); then
echo "$a is not equal to $b"
fi
if (( a < b )); then
echo "$a is less than $b"
fi
if (( a <= 10 )); then
echo "$a is less than or equal to 10"
fi
if (( b > a )); then
echo "$b is greater than $a"
fi
if (( b >= 20 )); then
echo "$b is greater than or equal to 20"
fi
┌─────────────────────────────────────────────────────────────────────┐
│ STRING COMPARISON OPERATORS │
└─────────────────────────────────────────────────────────────────────┘
┌──────────────────┬──────────────────────────────────────────────────┐
│ Operator │ Description │
├──────────────────┼──────────────────────────────────────────────────┤
│ = │ Equal to (or ==) │
│ != │ Not equal to │
│ < │ Less than (ASCII order) │
│ > │ Greater than (ASCII order) │
│ -z │ String is null (empty) │
│ -n │ String is not null │
└──────────────────┴──────────────────────────────────────────────────┘
string_compare.sh
#!/usr/bin/env bash
str1="apple"
str2="banana"
empty=""
space=" "
# Using [ ] (test command) - note quoting
if [ "$str1" = "apple" ]; then
echo "str1 equals apple"
fi
if [ "$str1" != "$str2" ]; then
echo "str1 is not equal to str2"
fi
# String comparison with < and > (in [ ], must escape or use [[ ]])
# These compare based on ASCII/locale order
if [[ "$str1" < "$str2" ]]; then
echo "apple comes before banana alphabetically"
fi
# Check empty string
if [ -z "$empty" ]; then
echo "empty string is null"
fi
if [ -n "$str1" ]; then
echo "str1 is not empty"
fi
# Common mistake: Unquoted variables
# WRONG: if [ $str1 = "apple" ]; then # Fails if str1 is empty
# RIGHT: if [ "$str1" = "apple" ]; then

┌─────────────────────────────────────────────────────────────────────┐
│ LOGICAL OPERATORS │
└─────────────────────────────────────────────────────────────────────┘
┌──────────────────┬──────────────────────────────────────────────────┐
│ Operator │ Description │
├──────────────────┼──────────────────────────────────────────────────┤
│ ! │ NOT (negation) │
│ && │ AND │
│ || │ OR │
│ -a │ AND (in [ ]) │
│ -o │ OR (in [ ]) │
└──────────────────┴──────────────────────────────────────────────────┘
logical.sh
#!/usr/bin/env bash
a=10
b=20
# NOT
if [ ! "$a" -eq 10 ]; then
echo "a is not 10"
else
echo "a is 10"
fi
# AND - multiple conditions must be true
if [ "$a" -gt 5 ] && [ "$a" -lt 15 ]; then
echo "a is between 5 and 15"
fi
# OR - at least one condition must be true
if [ "$a" -eq 10 ] || [ "$b" -eq 10 ]; then
echo "At least one equals 10"
fi
# Using -a and -o in single brackets
if [ "$a" -gt 5 -a "$a" -lt 15 ]; then
echo "a is between 5 and 15 (using -a)"
fi
# Using [[ ]] with && and || (preferred)
if [[ "$a" -gt 5 && "$a" -lt 15 ]]; then
echo "a is between 5 and 15 (using [[ ]])"
fi
negation.sh
#!/usr/bin/env bash
# Negate file test
if [ ! -f "/etc/passwd" ]; then
echo "File does not exist"
else
echo "File exists"
fi
# Negate string test
name=""
if [ ! -n "$name" ]; then
echo "Name is empty"
fi
# Negate command success
if ! grep -q "pattern" file.txt; then
echo "Pattern not found"
fi

┌─────────────────────────────────────────────────────────────────────┐
│ FILE TEST OPERATORS │
└─────────────────────────────────────────────────────────────────────┘
┌──────────────────┬──────────────────────────────────────────────────┐
│ Operator │ Description │
├──────────────────┼──────────────────────────────────────────────────┤
│ -e │ File exists │
│ -f │ Regular file (not directory/device) │
│ -d │ Directory exists │
│ -b │ Block device │
│ -c │ Character device │
│ -p │ Named pipe (FIFO) │
│ -S │ Socket │
│ -L or -l │ Symbolic link │
│ -s │ File exists and is non-empty │
└──────────────────┴──────────────────────────────────────────────────┘
file_tests.sh
#!/usr/bin/env bash
file="/etc/passwd"
dir="/tmp"
# Check if file exists
if [ -e "$file" ]; then
echo "File exists"
fi
# Check if regular file
if [ -f "$file" ]; then
echo "$file is a regular file"
fi
# Check if directory
if [ -d "$dir" ]; then
echo "$dir is a directory"
fi
# Check if symbolic link
link="/usr/bin/python"
if [ -L "$link" ]; then
echo "$link is a symbolic link"
fi
# Check if file is non-empty
if [ -s "$file" ]; then
echo "$file exists and is not empty"
fi
┌─────────────────────────────────────────────────────────────────────┐
│ FILE PERMISSION OPERATORS │
└─────────────────────────────────────────────────────────────────────┘
┌──────────────────┬──────────────────────────────────────────────────┐
│ Operator │ Description │
├──────────────────┼──────────────────────────────────────────────────┤
│ -r │ File is readable │
│ -w │ File is writable │
│ -x │ File is executable │
│ -O │ Current user owns the file │
│ -G │ File group matches current user's group │
│ -u │ File has setuid bit set │
│ -g │ File has setgid bit set │
│ -k │ File has sticky bit set │
└──────────────────┴──────────────────────────────────────────────────┘
permission_tests.sh
#!/usr/bin/env bash
file="/tmp/test.txt"
# Check if readable
if [ -r "$file" ]; then
echo "File is readable"
fi
# Check if writable
if [ -w "$file" ]; then
echo "File is writable"
fi
# Check if executable
if [ -x "$file" ]; then
echo "File is executable"
fi
# Combined permission check
if [ -r "$file" ] && [ -w "$file" ]; then
echo "File has read and write permissions"
fi
# Check ownership
if [ -O "$file" ]; then
echo "You own this file"
fi
┌─────────────────────────────────────────────────────────────────────┐
│ FILE COMPARISON OPERATORS │
└─────────────────────────────────────────────────────────────────────┘
┌──────────────────┬──────────────────────────────────────────────────┐
│ Operator │ Description │
├──────────────────┼──────────────────────────────────────────────────┤
│ -nt │ File1 is newer than File2 │
│ -ot │ File1 is older than File2 │
│ -ef │ File1 and File2 are the same file (same inode) │
└──────────────────┴──────────────────────────────────────────────────┘
file_compare.sh
#!/usr/bin/env bash
file1="/tmp/file1.txt"
file2="/tmp/file2.txt"
# Check if file1 is newer
if [ "$file1" -nt "$file2" ]; then
echo "$file1 is newer than $file2"
fi
# Check if file1 is older
if [ "$file1" -ot "$file2" ]; then
echo "$file1 is older than $file2"
fi
# Check if same file (same inode)
if [ "$file1" -ef "$file2" ]; then
echo "$file1 and $file2 are the same file"
fi

compound.sh
#!/usr/bin/env bash
a=10
b=20
# Grouping conditions
if [ \( "$a" -gt 5 \) -a \( "$a" -lt 20 \) ]; then
echo "a is between 5 and 20"
fi
# Or use [[ ]] which doesn't need escaping
if [[ "$a" -gt 5 && "$a" -lt 20 ]]; then
echo "a is between 5 and 20 (cleaner syntax)"
fi
# Using ( ) for subshell
(
cd /tmp
echo "In subshell, pwd: $(pwd)"
)
echo "Back to main, pwd: $(pwd)"
# Using { } for grouping (in current shell)
{
cd /tmp
echo "In group, pwd: $(pwd)"
}
echo "After group, pwd: $(pwd)"
ternary.sh
#!/usr/bin/env bash
# Using arithmetic expansion for ternary-like behavior
a=10
# Condition ? true_value : false_value
result=$(( a > 5 ? "greater" : "smaller" ))
echo "Result: $result"
# More practical
status="running"
message=$([ "$status" = "running" ] && echo "All good!" || echo "Issues!")
echo "$message"
# With variables
a=15
b=10
max=$(( a > b ? a : b ))
min=$(( a < b ? a : b ))
echo "Max: $max, Min: $min"

health_check.sh
#!/usr/bin/env bash
set -euo pipefail
# Thresholds
CPU_THRESHOLD=80
MEMORY_THRESHOLD=85
DISK_THRESHOLD=90
# Check CPU usage
cpu_usage=$(top -bn1 | grep "Cpu(s)" | awk '{print $2}' | cut -d'%' -f1)
cpu_usage_int=${cpu_usage%.*} # Remove decimal
if [ "$cpu_usage_int" -gt "$CPU_THRESHOLD" ]; then
echo "WARNING: High CPU usage: ${cpu_usage}%"
else
echo "OK: CPU usage is ${cpu_usage}%"
fi
# Check memory usage
mem_usage=$(free | grep Mem | awk '{printf "%.0f", $3/$2 * 100.0}')
if [ "$mem_usage" -gt "$MEMORY_THRESHOLD" ]; then
echo "WARNING: High memory usage: ${mem_usage}%"
else
echo "OK: Memory usage is ${mem_usage}%"
fi
# Check disk usage
disk_usage=$(df -h / | tail -1 | awk '{print $5}' | tr -d '%')
if [ "$disk_usage" -gt "$DISK_THRESHOLD" ]; then
echo "WARNING: High disk usage: ${disk_usage}%"
else
echo "OK: Disk usage is ${disk_usage}%"
fi
service_check.sh
#!/usr/bin/env bash
SERVICE="nginx"
# Check if service is running
if systemctl is-active --quiet "$SERVICE"; then
echo "$SERVICE is running"
else
echo "$SERVICE is NOT running"
exit 1
fi
# Check if service is enabled
if systemctl is-enabled --quiet "$SERVICE"; then
echo "$SERVICE is enabled at boot"
else
echo "$SERVICE is NOT enabled at boot"
fi
version_compare.sh
#!/usr/bin/env bash
# Function to compare versions
# Returns 0 if v1 > v2, 1 if v1 < v2, 2 if equal
version_compare() {
if [ "$1" = "$2" ]; then
return 2
fi
local IFS=.
local i ver1=($1) ver2=($2)
for ((i=${#ver1[@]}; i<${#ver2[@]}; i++)); do
ver1[i]=0
done
for ((i=0; i<${#ver1[@]}; i++)); do
if [[ -z "${ver2[i]}" ]]; then
ver2[i]=0
fi
if ((10#${ver1[i]} > 10#${ver2[i]})); then
return 0
fi
if ((10#${ver1[i]} < 10#${ver2[i]})); then
return 1
fi
done
}
# Test
v1="1.2.3"
v2="1.2.10"
version_compare "$v1" "$v2"
result=$?
if [ $result -eq 0 ]; then
echo "$v1 > $v2"
elif [ $result -eq 1 ]; then
echo "$v1 < $v2"
else
echo "$v1 == $v2"
fi
validate.sh
#!/usr/bin/env bash
# Validate that input is a number
is_number() {
local re='^[0-9]+$'
[[ "$1" =~ $re ]]
}
# Validate IP address
is_ip() {
local re='^([0-9]{1,3}\.){3}[0-9]{1,3}$'
[[ "$1" =~ $re ]]
}
# Validate port number
is_port() {
[[ "$1" =~ ^[0-9]+$ ]] && [ "$1" -ge 1 ] && [ "$1" -le 65535 ]
}
# Usage
input="8080"
if is_number "$input"; then
echo "Valid number: $input"
else
echo "Not a number"
fi
if is_port "$input"; then
echo "Valid port: $input"
else
echo "Invalid port"
fi

┌─────────────────────────────────────────────────────────────────────┐
│ OPERATOR PRECEDENCE (High to Low) │
└─────────────────────────────────────────────────────────────────────┘
┌──────────────────┬──────────────────────────────────────────────────┐
│ Precedence │ Operators │
├──────────────────┼──────────────────────────────────────────────────┤
│ Highest │ ( ), !, - (unary), ~, ++, -- │
│ │ *, /, % │
│ │ +, - │
│ │ <<, >> │
│ │ <, <=, >, >= │
│ │ ==, != │
│ │ & (bitwise) │
│ │ ^ (bitwise XOR) │
│ │ | (bitwise OR) │
│ │ && │
│ Lowest │ || │
└──────────────────┴──────────────────────────────────────────────────┘
precedence.sh
#!/usr/bin/env bash
# Demonstrating precedence
# * and / have higher precedence than + and -
result=$(( 2 + 3 * 4 )) # 14, not 20
echo "2 + 3 * 4 = $result"
result=$(( (2 + 3) * 4 )) # 20
echo "(2 + 3) * 4 = $result"
# Logical operators
result=$(( 1 && 0 )) # 0 (AND)
echo "1 && 0 = $result"
result=$(( 1 || 0 )) # 1 (OR)
echo "1 || 0 = $result"
result=$(( ! 0 )) # 1 (NOT)
echo "! 0 = $result"

In this chapter, you learned about:

  • ✅ Arithmetic operators (+, -, *, /, %, etc.)
  • ✅ Increment and decrement operators
  • ✅ Bitwise operators
  • ✅ Numeric comparison operators (-eq, -ne, -lt, etc.)
  • ✅ String comparison operators (=, !=, <, >, -z, -n)
  • ✅ Logical operators (!, &&, ||, -a, -o)
  • ✅ File test operators (-f, -d, -r, -w, -x, etc.)
  • ✅ File permission operators
  • ✅ File comparison operators (-nt, -ot, -ef)
  • ✅ Compound operators
  • ✅ Operator precedence
  • ✅ Practical examples for DevOps

  1. Write a calculator script using arithmetic operators
  2. Create a script that compares two numbers
  3. Write a script that checks file permissions
  1. Implement a version comparison function
  2. Create a script that validates IP addresses and ports
  3. Write a script that checks multiple system health metrics
  1. Implement a complete input validation library
  2. Create a script that uses bitwise operators for flags
  3. Write a complex conditional expression parser

Continue to the next chapter to learn about control flow with conditionals.


Previous Chapter: Special Variables Next Chapter: Control Flow - Conditionals