Bash Conditional Logic: if, elif, else, and Tests

Input handling is useful, but decisions are where scripts become dynamic. In Bash, if logic is powered by tests and command exit codes.

Table of Contents

This post walks through practical conditional patterns for production scripts.

Core Control Flow

Test Syntax and Operators

Combining Conditions

Series Navigation

Core control flow

if elif else structure

Bash conditionals follow a simple structure that is easy to scan in larger scripts.

if [[ condition_a ]]; then
  echo "branch a"
elif [[ condition_b ]]; then
  echo "branch b"
else
  echo "fallback branch"
fi

Command exit codes as conditions

A command itself can be the condition.

if grep -q "production" config.env; then
  echo "production settings found"
else
  echo "production settings missing"
fi

This is one of the cleanest Bash idioms because it avoids storing temporary values.

Test syntax and operators

Test forms and when to use each

test and [ ] are equivalent in many cases, while [[ ]] offers safer string handling and pattern support.

test -f /etc/hosts && echo "exists"
[ -d /tmp ] && echo "tmp exists"
[[ -n "$USER" ]] && echo "user is set"

Prefer [[ ]] for script logic unless you need strict POSIX shell compatibility.

File checks

Common file checks:

  • -f for regular file
  • -d for directory
  • -x for executable
  • -r for readable file
  • -w for writable file
if [[ -x "./deploy.sh" ]]; then
  ./deploy.sh
else
  echo "deploy script is not executable"
fi

String checks

Use -z for empty strings and -n for non-empty strings.

if [[ -z "$api_token" ]]; then
  echo "missing API token"
  exit 1
fi

if [[ "$env" == "prod" ]]; then
  echo "running production workflow"
fi

Numeric checks

Use numeric operators for integer comparisons.

if [[ "$retry_count" -ge 3 ]]; then
  echo "max retries reached"
fi

Useful numeric operators include -eq, -ne, -lt, -le, -gt, and -ge.

Combining conditions

Use logical operators safely

You can combine expressions directly inside [[ ]].

if [[ -n "$env" && "$env" != "dev" ]]; then
  echo "non-dev environment selected"
fi

if [[ "$size_mb" -gt 100 || "$force" == "true" ]]; then
  echo "continue with upload"
fi

Build readable conditions

When conditions get complex, split them into named variables for clarity.

has_token=false
is_prod=false

[[ -n "$api_token" ]] && has_token=true
[[ "$env" == "prod" ]] && is_prod=true

if [[ "$has_token" == "true" && "$is_prod" == "true" ]]; then
  echo "production deployment can proceed"
fi

Practical script example

#!/usr/bin/env bash

env_name="${1:-dev}"
config_file="./config/${env_name}.env"

if [[ ! -f "$config_file" ]]; then
  echo "config file not found: $config_file"
  exit 1
fi

if [[ "$env_name" == "prod" ]]; then
  echo "running production checks"
elif [[ "$env_name" == "stage" ]]; then
  echo "running stage checks"
else
  echo "running dev checks"
fi

Next in this series

Next, we will use case statements to replace long if chains when matching subcommands and pattern-based options.