Windows How-To

Use Bash if…else Statement

Build reliable conditionals in Bash using [[ ]], elif, and logical operators for strings, numbers, and files.

Build reliable conditionals in Bash using [[ ]], elif, and logical operators for strings, numbers, and files.

Branches in Bash often execute the wrong path when string comparisons use the numeric operator -eq or when variables are left unquoted. Switching to Bash’s [[ ]] test with the correct operators fixes those logic errors and makes scripts easier to maintain.

Method 1: Write if…else with Bash [[ ]] (strings, numbers, and files)

Create a new script and add a minimal conditional using the modern Bash test syntax [[ ]]. This form avoids accidental glob expansion and word splitting, which reduces surprising behavior compared to single brackets.
#!/usr/bin/env bash
set -euo pipefail

read -r -p "Enter a value: " val

if [[ "$val" == "admin" ]]; then
  echo "Welcome, admin."
else
  echo "Access limited."
fi
Compare strings with == or !=. Quote variables to keep empty values and spaces from breaking your tests. This prevents unintended matches caused by unquoted expansions.
user="alice"
target="alice"

if [[ "$user" == "$target" ]]; then
  echo "Usernames match."
else
  echo "Usernames differ."
fi
Use numeric operators for integers inside [[ ]]: -eq, -ne, -gt, -ge, -lt, -le. These operate on numbers only and avoid lexicographic comparisons.
read -r -p "Enter a number: " n

if [[ "$n" -gt 10 ]]; then
  echo "Greater than 10."
elif [[ "$n" -eq 10 ]]; then
  echo "Equal to 10."
else
  echo "Less than 10."
fi
Check files with file-test operators. These help you branch based on the filesystem state without running external commands.
  • -f path: regular file exists.
  • -d path: directory exists.
  • -e path: path exists (any type).
  • -x path: executable file exists.
  • -r path / -w path: readable / writable.
path="./data.txt"

if [[ -f "$path" ]]; then
  echo "File exists."
else
  echo "Creating file..."
  : > "$path"
fi
Combine conditions with && (AND), || (OR), and ! (NOT). Group them with parentheses for clarity and correct precedence.
file="./report.log"
size=5

# True when the file exists AND size is at least 5
if [[ -f "$file" ]] && [[ "$size" -ge 5 ]]; then
  echo "Process report."
fi

# True when user is admin OR staff
role="staff"
if [[ "$role" == "admin" || "$role" == "staff" ]]; then
  echo "Privileged."
fi

# Negation: path exists but is not a directory
if [[ -e "$file" && ! -d "$file" ]]; then
  echo "Path is not a directory."
fi
Use elif to short-circuit checks. Once one condition is true, later branches are skipped, which speeds up evaluation and reduces unnecessary commands.
status="warning"

if [[ "$status" == "error" ]]; then
  echo "Exit immediately."
  exit 1
elif [[ "$status" == "warning" ]]; then
  echo "Log and continue."
else
  echo "All good."
fi

Method 2: Compare integers with (( )) arithmetic evaluation

Use arithmetic contexts for numeric logic. Inside (( )), you can write math-like comparisons (<, >, ==) without quoting variables. This is concise and fast for integer-only checks.
a=12 b=8

if (( a > b )); then
  echo "a is larger."
elif (( a == b )); then
  echo "Equal."
else
  echo "b is larger."
fi
Combine boolean operators directly in arithmetic context. This reduces boilerplate when evaluating multiple numeric conditions.
x=7 y=3 z=10

if (( (x > y) && (z >= 10) )); then
  echo "Threshold met."
fi
Validate input. Arithmetic contexts treat non-numeric values as zero. If input may be non-numeric, guard it with a regex test in [[ ]] before evaluating.
read -r n
if [[ "$n" =~ ^-?[0-9]+$ ]] && (( n >= 0 )); then
  echo "Non-negative integer."
else
  echo "Invalid number."
fi

Method 3: Use POSIX-portable [ ] for broader shell compatibility

Prefer single brackets when targeting shells beyond Bash (e.g., /bin/sh). Remember that [ is a command: spaces and quoting are required.
#!/bin/sh

a="hello"
b="hello"

if [ "$a" = "$b" ]; then
  echo "Match."
else
  echo "No match."
fi
Use numeric operators for integers and quote variables to prevent word splitting. This avoids bugs when variables are empty or contain spaces.
a=5
b=30

if [ "$a" -lt "$b" ]; then
  echo "a is less than b."
fi
When doing lexicographic string comparisons with < or >, escape the operators to prevent redirection. Many errors stem from forgetting this.
x="apple" y="banana"

if [ "$x" \< "$y" ]; then
  echo "apple comes before banana."
fi
Use file tests the same way as in [[ ]], but keep everything quoted to avoid edge cases.
file="./cfg.ini"
if [ -r "$file" ] && [ -w "$file" ]; then
  echo "Config is readable and writable."
fi

Method 4: Replace long chains with case for value-based branching

Use case to simplify multiple equality checks on a single variable. This reduces nested if blocks and improves readability and speed when matching patterns.
read -r -p "Enter mode (start|stop|status): " mode

case "$mode" in
  start)  echo "Starting...";;
  stop)   echo "Stopping...";;
  status) echo "Service is running.";;
  *)      echo "Unknown mode."; exit 1;;
esac
Match multiple patterns easily. This keeps your logic compact and avoids repeated variable comparisons.
level="warn"

case "$level" in
  error|err)  echo "Exit with failure."; exit 1;;
  warn|warning) echo "Log warning.";;

  info|debug) echo "Proceed normally.";;
  *)          echo "Unrecognized level.";;
esac

Method 5: Test multiple conditions correctly and debug failures

Group boolean logic to reflect your truth table. For example, “require Z or both X and Y” becomes if [[ "$Z" == "TRUE" || ( "$X" == "TRUE" && "$Y" == "TRUE" ) ]]. Parentheses clarify precedence and prevent subtle bugs.
X="TRUE" Y="FALSE" Z="TRUE"

if [[ "$Z" == "TRUE" || ( "$X" == "TRUE" && "$Y" == "TRUE" ) ]]; then
  echo "Conditions met."
else
  echo "Conditions not met."
fi
Choose the right operators. Use == for strings and -eq for integers. Mixing them causes incorrect branches (e.g., string compared with -eq always fails).
Quote variable expansions in tests, especially with single brackets. Quoting avoids crashes when variables are empty and prevents wildcard expansion against filesystem entries.
Trace logic when results look wrong. Enable xtrace to see each expanded command and condition as Bash evaluates it.
#!/usr/bin/env bash
set -x  # turn on trace
# ... your conditionals here ...
set +x  # turn off trace
Keep scripts consistent and readable. Indent the body under then/else, leave spaces around brackets, and favor early exits on error branches to reduce nesting.

Reference: Common test operators at a glance

This quick list helps you pick the right operator for the job.

  • Strings: ==, !=, -n var (length > 0), -z var (empty).
  • Integers: -eq, -ne, -gt, -ge, -lt, -le; or use (( )) with <, >, ==.
  • Files: -f (regular), -d (directory), -e (exists), -x (executable), -r (readable), -w (writable).
  • Logic: && (AND), || (OR), ! (NOT); group with ( ... ) inside [[ ]].

With the right test form, correct operators, and consistent quoting, your Bash conditionals execute the intended branch every time. Keep [[ ]] for Bash scripts, use (( )) for math, and fall back to [ ] only when portability is a must.