Introduction to Conditional Statements
Conditional statements are fundamental to programming in Bash. They allow your scripts to make decisions based on conditions, execute different code paths, and handle various scenarios. The if...else construct is the primary way to implement conditional logic in Bash.
Key Concepts
- Exit Status: Every command returns a status (0 for success, non-zero for failure)
- Test Command:
[ ]or[[ ]]for evaluating conditions - Conditional Execution: Different paths based on condition results
- Nested Conditions: Multiple levels of decision-making
- Logical Operators: Combine multiple conditions
1. Basic If Statement Syntax
Simple If Statement
#!/bin/bash # Basic if statement if condition; then # commands to execute if condition is true echo "Condition is true" fi # Example with test command if [ "$name" = "John" ]; then echo "Hello John!" fi # Example with command exit status if grep -q "error" logfile.txt; then echo "Found errors in log file" fi # One-line if (not recommended for complex conditions) if [ -f "$file" ]; then echo "File exists"; fi
If-Else Statement
#!/bin/bash # Basic if-else if [ "$age" -ge 18 ]; then echo "You are an adult" else echo "You are a minor" fi # Example with multiple commands if cp file1.txt file2.txt; then echo "Copy successful" echo "File copied to file2.txt" else echo "Copy failed" >&2 exit 1 fi
If-Elif-Else Statement
#!/bin/bash
# Multiple conditions with elif
if [ "$score" -ge 90 ]; then
echo "Grade: A"
echo "Excellent work!"
elif [ "$score" -ge 80 ]; then
echo "Grade: B"
echo "Good job!"
elif [ "$score" -ge 70 ]; then
echo "Grade: C"
echo "Satisfactory"
elif [ "$score" -ge 60 ]; then
echo "Grade: D"
echo "Needs improvement"
else
echo "Grade: F"
echo "Failed"
fi
# Real-world example
if [ "$1" = "start" ]; then
systemctl start myservice
echo "Service started"
elif [ "$1" = "stop" ]; then
systemctl stop myservice
echo "Service stopped"
elif [ "$1" = "restart" ]; then
systemctl restart myservice
echo "Service restarted"
elif [ "$1" = "status" ]; then
systemctl status myservice
else
echo "Usage: $0 {start|stop|restart|status}"
exit 1
fi
2. Test Conditions and Operators
File Test Operators
#!/bin/bash file="document.txt" dir="/home/user" link="/path/to/symlink" # File existence and type if [ -e "$file" ]; then echo "File exists"; fi if [ -f "$file" ]; then echo "Regular file"; fi if [ -d "$dir" ]; then echo "Directory"; fi if [ -L "$link" ]; then echo "Symbolic link"; fi if [ -b "$file" ]; then echo "Block device"; fi if [ -c "$file" ]; then echo "Character device"; fi if [ -p "$file" ]; then echo "Named pipe"; fi if [ -S "$file" ]; then echo "Socket"; fi # File permissions if [ -r "$file" ]; then echo "Readable"; fi if [ -w "$file" ]; then echo "Writable"; fi if [ -x "$file" ]; then echo "Executable"; fi # File size if [ -s "$file" ]; then echo "File not empty"; fi # File ownership if [ -O "$file" ]; then echo "Owned by effective user"; fi if [ -G "$file" ]; then echo "Owned by effective group"; fi # File comparison if [ "$file1" -nt "$file2" ]; then echo "file1 newer than file2"; fi if [ "$file1" -ot "$file2" ]; then echo "file1 older than file2"; fi if [ "$file1" -ef "$file2" ]; then echo "Same file (hard links)"; fi # Examples backup_file="data.backup" if [ -f "$backup_file" ] && [ -s "$backup_file" ]; then echo "Backup exists and is not empty" fi config_dir="/etc/myapp" if [ -d "$config_dir" ] && [ -r "$config_dir" ] && [ -x "$config_dir" ]; then echo "Config directory accessible" fi
String Test Operators
#!/bin/bash
str1="hello"
str2="world"
empty=""
# String length
if [ -z "$empty" ]; then echo "String is empty"; fi
if [ -n "$str1" ]; then echo "String is not empty"; fi
# String equality
if [ "$str1" = "$str2" ]; then echo "Strings are equal"; fi
if [ "$str1" != "$str2" ]; then echo "Strings are not equal"; fi
# String comparison (lexicographic)
if [ "$str1" \< "$str2" ]; then echo "$str1 comes before $str2"; fi
if [ "$str1" \> "$str2" ]; then echo "$str1 comes after $str2"; fi
# Pattern matching with [[ ]]
if [[ "$filename" == *.txt ]]; then echo "Text file"; fi
if [[ "$filename" == [a-z]* ]]; then echo "Starts with lowercase"; fi
if [[ "$filename" == *[0-9]* ]]; then echo "Contains numbers"; fi
# Regular expression matching
if [[ "$email" =~ ^[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,}$ ]]; then
echo "Valid email format"
fi
# Examples
read -p "Enter yes/no: " answer
if [[ "${answer,,}" =~ ^(yes|y)$ ]]; then
echo "You agreed"
elif [[ "${answer,,}" =~ ^(no|n)$ ]]; then
echo "You disagreed"
else
echo "Invalid input"
fi
# Check if string contains substring
if [[ "$str" == *"substring"* ]]; then
echo "Contains substring"
fi
Numeric Test Operators
#!/bin/bash a=10 b=20 c=10 # Numeric comparisons if [ "$a" -eq "$c" ]; then echo "a equals c"; fi if [ "$a" -ne "$b" ]; then echo "a not equal to b"; fi if [ "$a" -lt "$b" ]; then echo "a less than b"; fi if [ "$a" -le "$c" ]; then echo "a less than or equal to c"; fi if [ "$b" -gt "$a" ]; then echo "b greater than a"; fi if [ "$b" -ge "$a" ]; then echo "b greater than or equal to a"; fi # Arithmetic evaluation with double parentheses if (( a == c )); then echo "a equals c"; fi if (( a != b )); then echo "a not equal to b"; fi if (( a < b )); then echo "a less than b"; fi if (( a <= c )); then echo "a less than or equal to c"; fi if (( b > a )); then echo "b greater than a"; fi if (( b >= a )); then echo "b greater than or equal to a"; fi # Complex arithmetic if (( (a + b) > 25 && (b - a) < 20 )); then echo "Complex condition met" fi # Examples score=85 if (( score >= 90 )); then grade="A" elif (( score >= 80 )); then grade="B" elif (( score >= 70 )); then grade="C" elif (( score >= 60 )); then grade="D" else grade="F" fi echo "Grade: $grade" # Modulo operation number=7 if (( number % 2 == 0 )); then echo "Even" else echo "Odd" fi
Logical Operators
#!/bin/bash
# AND operator - both conditions must be true
if [ -f "$file" ] && [ -r "$file" ]; then
echo "File exists and is readable"
fi
# Using && with [[ ]]
if [[ -f "$file" && -r "$file" ]]; then
echo "File exists and is readable"
fi
# Multiple AND conditions
if [ "$age" -ge 18 ] && [ "$age" -le 65 ] && [ "$citizen" = "yes" ]; then
echo "Eligible to vote"
fi
# OR operator - at least one condition must be true
if [ "$day" = "Saturday" ] || [ "$day" = "Sunday" ]; then
echo "It's the weekend!"
fi
# Using || with [[ ]]
if [[ "$day" == "Saturday" || "$day" == "Sunday" ]]; then
echo "Weekend!"
fi
# Combining AND and OR
if [ -f "$file" ] && ( [ -r "$file" ] || [ -w "$file" ] ); then
echo "File exists and is either readable or writable"
fi
# NOT operator
if [ ! -f "$file" ]; then
echo "File does not exist"
fi
if [[ ! -d "$dir" ]]; then
echo "Directory does not exist"
fi
if ! command -v git &> /dev/null; then
echo "Git is not installed"
exit 1
fi
# Complex conditions
if { [ -f "$file" ] || [ -d "$file" ]; } && [ -r "$file" ]; then
echo "Path exists (file or directory) and is readable"
fi
# Examples
user_input="yes"
if [[ "${user_input,,}" =~ ^(y|yes)$ ]]; then
echo "User confirmed"
fi
# Check multiple conditions
required_tools=("git" "make" "gcc")
missing_tools=()
for tool in "${required_tools[@]}"; do
if ! command -v "$tool" &> /dev/null; then
missing_tools+=("$tool")
fi
done
if [ ${#missing_tools[@]} -eq 0 ]; then
echo "All required tools are installed"
else
echo "Missing tools: ${missing_tools[*]}"
fi
3. Advanced If Statements
Using Double Brackets [[ ]]
#!/bin/bash
# [[ ]] is bash-specific, more powerful than [ ]
# Pattern matching
if [[ "$filename" == *.txt ]]; then
echo "Text file"
fi
if [[ "$filename" == [a-z]*.txt ]]; then
echo "Text file starting with lowercase"
fi
# Regular expression matching
if [[ "$email" =~ ^[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,}$ ]]; then
echo "Valid email"
fi
# No word splitting or pathname expansion
var="a b c"
if [[ $var == "a b c" ]]; then # No quotes needed
echo "Matches"
fi
# Safer with empty variables
unset var
if [[ -n $var ]]; then # No error if var is unset
echo "var is set"
fi
# Combining conditions
if [[ -f "$file" && -r "$file" || -w "$file" ]]; then
echo "File exists and is readable or writable"
fi
# Parentheses for grouping
if [[ (-f "$file" || -d "$file") && -r "$file" ]]; then
echo "Path exists (file or dir) and is readable"
fi
# Examples
ip="192.168.1.100"
if [[ $ip =~ ^([0-9]{1,3}\.){3}[0-9]{1,3}$ ]]; then
echo "Valid IP address format"
fi
path="/home/user/docs/file.txt"
if [[ $path == *.txt && -f $path ]]; then
echo "Text file exists"
fi
Using Double Parentheses (( ))
#!/bin/bash # (( )) for arithmetic evaluation # Basic arithmetic if (( a > b )); then echo "a is greater than b" fi # Complex expressions if (( (a + b) * c > 100 )); then echo "Result > 100" fi # Increment/Decrement counter=5 if (( counter++ > 5 )); then echo "Counter was > 5 before increment" else echo "Counter was <= 5 before increment, now: $counter" fi # Bitwise operations flags=0b1010 if (( flags & 0b0010 )); then echo "Bit 1 is set" fi # Ternary-like expression max=$(( a > b ? a : b )) # Multiple conditions if (( a > b && c < d || e == f )); then echo "Complex condition true" fi # Examples number=42 if (( number % 2 == 0 )); then echo "$number is even" fi year=2024 if (( year % 400 == 0 )) || (( year % 4 == 0 && year % 100 != 0 )); then echo "$year is a leap year" fi total=95 passed=85 if (( total >= passed )); then echo "Passed" else echo "Failed" fi
Nested If Statements
#!/bin/bash
# Basic nesting
if [ -f "$file" ]; then
echo "File exists"
if [ -r "$file" ]; then
echo "File is readable"
if [ -s "$file" ]; then
echo "File is not empty"
else
echo "File is empty"
fi
else
echo "File is not readable"
fi
else
echo "File does not exist"
fi
# Practical example: User authentication simulation
read -p "Username: " username
if [ -n "$username" ]; then
read -sp "Password: " password
echo
if [ ${#password} -ge 8 ]; then
if [ "$username" = "admin" ] && [ "$password" = "secret123" ]; then
echo "Admin login successful"
elif [ "$username" = "user" ] && [ "$password" = "pass1234" ]; then
echo "User login successful"
else
echo "Invalid credentials"
fi
else
echo "Password too short"
fi
else
echo "Username required"
fi
# Configuration validation
validate_config() {
local config_file="$1"
if [ -f "$config_file" ]; then
echo "Config file found"
if [ -r "$config_file" ]; then
echo "Config file readable"
# Check required settings
if grep -q "^server=" "$config_file"; then
server=$(grep "^server=" "$config_file" | cut -d= -f2)
if [ -n "$server" ]; then
echo "Server: $server"
if grep -q "^port=" "$config_file"; then
port=$(grep "^port=" "$config_file" | cut -d= -f2)
if [[ "$port" =~ ^[0-9]+$ ]] && [ "$port" -ge 1024 ] && [ "$port" -le 65535 ]; then
echo "Valid port: $port"
return 0
else
echo "Invalid port number"
fi
else
echo "Missing port setting"
fi
else
echo "Empty server setting"
fi
else
echo "Missing server setting"
fi
else
echo "Config file not readable"
fi
else
echo "Config file not found"
fi
return 1
}
4. Conditional Command Execution
Using && and ||
#!/bin/bash
# && executes next command only if previous succeeded
mkdir newdir && cd newdir && touch file.txt
echo "Directory created and file created"
# || executes next command only if previous failed
command1 || command2 || echo "Both commands failed"
# Combining && and ||
[ -f "$file" ] && echo "File exists" || echo "File not found"
# Practical examples
# Create directory if it doesn't exist
[ -d "$dir" ] || mkdir -p "$dir"
# Check and install package
command -v git &>/dev/null || sudo apt-get install -y git
# Conditional execution chain
compile() {
gcc -c program.c && \
gcc -o program program.o && \
echo "Compilation successful" || \
{ echo "Compilation failed" >&2; exit 1; }
}
# Short-circuit evaluation
debug && echo "Debug mode enabled"
# Safe deletion
[ -f "$file" ] && rm "$file"
# Complex chain
test -f "$file" && \
cp "$file" "$backup" && \
gzip "$backup" && \
echo "Backup created" || \
echo "Backup failed"
Ternary-like Expressions
#!/bin/bash
# Using && and || as ternary operator
result=$([ "$a" -gt "$b" ] && echo "a" || echo "b")
# Using arithmetic ternary
max=$(( a > b ? a : b ))
# Function-like ternary
ternary() {
if eval "$1"; then
echo "$2"
else
echo "$3"
fi
}
status=$(ternary "[ -f '$file' ]" "exists" "missing")
# Practical examples
# Set variable based on condition
log_level=$([ "$debug" = true ] && echo "DEBUG" || echo "INFO")
# Command with default
editor=${VISUAL:-${EDITOR:-vi}}
# Path with fallback
config_file=${1:-/etc/default/config}
# Examples
is_root=$([ "$EUID" -eq 0 ] && echo "yes" || echo "no")
echo "Running as root: $is_root"
# File type indicator
file_type=$([ -f "$path" ] && echo "file" || ([ -d "$path" ] && echo "dir" || echo "other"))
5. Common Patterns and Use Cases
Input Validation
#!/bin/bash
# Validate required arguments
if [ $# -eq 0 ]; then
echo "Error: No arguments provided"
echo "Usage: $0 <filename>"
exit 1
fi
# Validate file input
validate_file() {
local file="$1"
if [ -z "$file" ]; then
echo "Error: No filename provided"
return 1
fi
if [ ! -e "$file" ]; then
echo "Error: File does not exist: $file"
return 1
fi
if [ ! -f "$file" ]; then
echo "Error: Not a regular file: $file"
return 1
fi
if [ ! -r "$file" ]; then
echo "Error: File not readable: $file"
return 1
fi
return 0
}
# Validate numeric input
validate_number() {
local num="$1"
local min="${2:-}"
local max="${3:-}"
# Check if it's a number
if ! [[ "$num" =~ ^-?[0-9]+$ ]]; then
echo "Error: Not a valid number: $num"
return 1
fi
# Check minimum
if [ -n "$min" ] && [ "$num" -lt "$min" ]; then
echo "Error: Number must be >= $min"
return 1
fi
# Check maximum
if [ -n "$max" ] && [ "$num" -gt "$max" ]; then
echo "Error: Number must be <= $max"
return 1
fi
return 0
}
# Validate email
validate_email() {
local email="$1"
if [[ "$email" =~ ^[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,}$ ]]; then
return 0
else
echo "Error: Invalid email format: $email"
return 1
fi
}
# Menu validation
show_menu() {
echo "1. Option 1"
echo "2. Option 2"
echo "3. Option 3"
echo "4. Quit"
}
while true; do
show_menu
read -p "Choose option [1-4]: " choice
case "$choice" in
1) echo "Option 1 selected" ;;
2) echo "Option 2 selected" ;;
3) echo "Option 3 selected" ;;
4) echo "Goodbye"; exit 0 ;;
*) echo "Invalid option, please try again" ;;
esac
done
Configuration File Parsing
#!/bin/bash
# Parse simple config file
parse_config() {
local config_file="$1"
if [ ! -f "$config_file" ]; then
echo "Error: Config file not found"
return 1
fi
while IFS='=' read -r key value; do
# Skip comments and empty lines
if [[ "$key" =~ ^[[:space:]]*# ]] || [ -z "$key" ]; then
continue
fi
# Trim whitespace
key=$(echo "$key" | xargs)
value=$(echo "$value" | xargs)
# Validate key
if [ -z "$key" ]; then
echo "Warning: Invalid line: $key=$value"
continue
fi
# Set variable based on key
case "$key" in
server)
if [ -n "$value" ]; then
SERVER="$value"
else
echo "Warning: server value empty"
fi
;;
port)
if [[ "$value" =~ ^[0-9]+$ ]] && [ "$value" -ge 1 ] && [ "$value" -le 65535 ]; then
PORT="$value"
else
echo "Warning: Invalid port: $value"
fi
;;
debug)
if [[ "$value" =~ ^(yes|true|1)$ ]]; then
DEBUG=true
elif [[ "$value" =~ ^(no|false|0)$ ]]; then
DEBUG=false
else
echo "Warning: Invalid debug value: $value"
fi
;;
*)
echo "Warning: Unknown config key: $key"
;;
esac
done < "$config_file"
}
# Example usage
CONFIG_FILE="app.conf"
if [ -f "$CONFIG_FILE" ]; then
parse_config "$CONFIG_FILE"
# Check required settings
if [ -z "$SERVER" ]; then
echo "Error: server not configured"
exit 1
fi
if [ -z "$PORT" ]; then
echo "Warning: port not set, using default 8080"
PORT=8080
fi
echo "Configuration loaded:"
echo " Server: $SERVER"
echo " Port: $PORT"
echo " Debug: ${DEBUG:-false}"
else
echo "Using default configuration"
SERVER="localhost"
PORT=8080
DEBUG=false
fi
Error Handling Functions
#!/bin/bash
# Comprehensive error handling
set -euo pipefail
# Error function
error_exit() {
echo "Error: $1" >&2
exit "${2:-1}"
}
# Warning function
warning() {
echo "Warning: $1" >&2
}
# Info function
info() {
echo "Info: $1"
}
# Check prerequisites
check_prerequisites() {
local missing=()
for cmd in "$@"; do
if ! command -v "$cmd" &>/dev/null; then
missing+=("$cmd")
fi
done
if [ ${#missing[@]} -gt 0 ]; then
error_exit "Missing required commands: ${missing[*]}"
fi
}
# Check file with specific permissions
check_file() {
local file="$1"
local required_perms="${2:-r}"
if [ ! -e "$file" ]; then
error_exit "File not found: $file"
fi
if [ ! -f "$file" ]; then
error_exit "Not a regular file: $file"
fi
if [[ "$required_perms" == *"r"* ]] && [ ! -r "$file" ]; then
error_exit "File not readable: $file"
fi
if [[ "$required_perms" == *"w"* ]] && [ ! -w "$file" ]; then
error_exit "File not writable: $file"
fi
if [[ "$required_perms" == *"x"* ]] && [ ! -x "$file" ]; then
error_exit "File not executable: $file"
fi
return 0
}
# Check directory
check_dir() {
local dir="$1"
local create="${2:-false}"
if [ ! -d "$dir" ]; then
if [ "$create" = true ]; then
mkdir -p "$dir" || error_exit "Cannot create directory: $dir"
info "Created directory: $dir"
else
error_exit "Directory not found: $dir"
fi
fi
if [ ! -w "$dir" ]; then
error_exit "Directory not writable: $dir"
fi
return 0
}
# Check disk space
check_disk_space() {
local path="$1"
local required_mb="${2:-100}"
local available_kb=$(df "$path" | awk 'NR==2 {print $4}')
local available_mb=$((available_kb / 1024))
if [ "$available_mb" -lt "$required_mb" ]; then
error_exit "Insufficient disk space: ${available_mb}MB available, ${required_mb}MB required"
fi
info "Disk space OK: ${available_mb}MB available"
}
# Usage example
main() {
info "Starting script..."
# Check prerequisites
check_prerequisites "curl" "grep" "sed"
# Check configuration file
check_file "/etc/app/config.conf" "r"
# Check log directory
check_dir "/var/log/app" true
# Check disk space
check_disk_space "/var/log" 100
info "All checks passed, continuing..."
}
# Run main with error handling
main "$@" || error_exit "Script failed"
6. Practical Examples
System Administration Script
#!/bin/bash
# system_check.sh - Comprehensive system check
set -euo pipefail
# Colors for output
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[0;33m'
BLUE='\033[0;34m'
NC='\033[0m'
# Helper functions
print_status() {
local status="$1"
local message="$2"
case "$status" in
OK) echo -e "${GREEN}[OK]${NC} $message" ;;
WARN) echo -e "${YELLOW}[WARN]${NC} $message" ;;
ERROR) echo -e "${RED}[ERROR]${NC} $message" ;;
INFO) echo -e "${BLUE}[INFO]${NC} $message" ;;
esac
}
# Check CPU load
check_cpu() {
local load=$(uptime | awk -F'load average:' '{print $2}' | cut -d, -f1 | xargs)
local cores=$(nproc)
local threshold=$(echo "$cores * 0.7" | bc)
if (( $(echo "$load > $threshold" | bc -l) )); then
print_status "WARN" "CPU load high: $load (threshold: $threshold)"
else
print_status "OK" "CPU load normal: $load"
fi
}
# Check memory usage
check_memory() {
local total=$(free -m | awk 'NR==2{print $2}')
local used=$(free -m | awk 'NR==2{print $3}')
local percent=$((used * 100 / total))
if [ "$percent" -gt 90 ]; then
print_status "ERROR" "Memory critical: ${used}MB/${total}MB (${percent}%)"
elif [ "$percent" -gt 80 ]; then
print_status "WARN" "Memory high: ${used}MB/${total}MB (${percent}%)"
else
print_status "OK" "Memory normal: ${used}MB/${total}MB (${percent}%)"
fi
}
# Check disk usage
check_disk() {
local threshold=80
df -h | grep -E '^/dev/' | while read line; do
local filesystem=$(echo "$line" | awk '{print $1}')
local usage=$(echo "$line" | awk '{print $5}' | sed 's/%//')
local mount=$(echo "$line" | awk '{print $6}')
if [ "$usage" -gt "$threshold" ]; then
print_status "WARN" "Disk $filesystem ($mount) at ${usage}%"
else
print_status "OK" "Disk $filesystem ($mount) at ${usage}%"
fi
done
}
# Check running services
check_services() {
local services=("ssh" "cron" "rsyslog")
for service in "${services[@]}"; do
if systemctl is-active --quiet "$service"; then
print_status "OK" "Service $service is running"
else
print_status "ERROR" "Service $service is not running"
fi
done
}
# Check network connectivity
check_network() {
local hosts=("8.8.8.8" "1.1.1.1")
for host in "${hosts[@]}"; do
if ping -c 1 -W 2 "$host" &>/dev/null; then
print_status "OK" "Network connectivity to $host"
else
print_status "WARN" "Cannot reach $host"
fi
done
}
# Check recent errors in logs
check_logs() {
local logfile="/var/log/syslog"
local errors=$(grep -i "error" "$logfile" 2>/dev/null | tail -5)
if [ -n "$errors" ]; then
print_status "WARN" "Recent errors found in logs:"
echo "$errors" | sed 's/^/ /'
else
print_status "OK" "No recent errors in logs"
fi
}
# Main function
main() {
print_status "INFO" "Starting system check at $(date)"
echo "----------------------------------------"
check_cpu
check_memory
check_disk
check_services
check_network
check_logs
echo "----------------------------------------"
print_status "INFO" "System check completed at $(date)"
}
# Run main
main
User Input Validation Script
#!/bin/bash
# input_validation.sh - Comprehensive input validation
set -euo pipefail
# Color codes
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[0;33m'
NC='\033[0m'
# Validation functions
validate_required() {
local value="$1"
local field="$2"
if [ -z "$value" ]; then
echo -e "${RED}Error: $field is required${NC}"
return 1
fi
return 0
}
validate_length() {
local value="$1"
local min="$2"
local max="$3"
local field="$4"
local len=${#value}
if [ "$len" -lt "$min" ]; then
echo -e "${RED}Error: $field must be at least $min characters${NC}"
return 1
fi
if [ "$len" -gt "$max" ]; then
echo -e "${RED}Error: $field must not exceed $max characters${NC}"
return 1
fi
return 0
}
validate_pattern() {
local value="$1"
local pattern="$2"
local field="$3"
if [[ ! "$value" =~ $pattern ]]; then
echo -e "${RED}Error: $field has invalid format${NC}"
return 1
fi
return 0
}
validate_range() {
local value="$1"
local min="$2"
local max="$3"
local field="$4"
if [ "$value" -lt "$min" ] || [ "$value" -gt "$max" ]; then
echo -e "${RED}Error: $field must be between $min and $max${NC}"
return 1
fi
return 0
}
validate_email() {
local email="$1"
local pattern="^[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,}$"
if [[ ! "$email" =~ $pattern ]]; then
echo -e "${RED}Error: Invalid email address${NC}"
return 1
fi
return 0
}
validate_phone() {
local phone="$1"
# Remove common formatting characters
phone=$(echo "$phone" | tr -d '[-.() ]')
if [[ ! "$phone" =~ ^[0-9]{10,15}$ ]]; then
echo -e "${RED}Error: Invalid phone number (must be 10-15 digits)${NC}"
return 1
fi
return 0
}
validate_date() {
local date="$1"
local format="$2"
case "$format" in
YYYY-MM-DD)
if [[ ! "$date" =~ ^[0-9]{4}-[0-9]{2}-[0-9]{2}$ ]]; then
echo -e "${RED}Error: Date must be in YYYY-MM-DD format${NC}"
return 1
fi
;;
MM/DD/YYYY)
if [[ ! "$date" =~ ^[0-9]{2}/[0-9]{2}/[0-9]{4}$ ]]; then
echo -e "${RED}Error: Date must be in MM/DD/YYYY format${NC}"
return 1
fi
;;
*)
return 1
;;
esac
return 0
}
validate_password() {
local password="$1"
local errors=()
# Length check
if [ ${#password} -lt 8 ]; then
errors+=("at least 8 characters")
fi
# Uppercase check
if ! [[ "$password" =~ [A-Z] ]]; then
errors+=("an uppercase letter")
fi
# Lowercase check
if ! [[ "$password" =~ [a-z] ]]; then
errors+=("a lowercase letter")
fi
# Digit check
if ! [[ "$password" =~ [0-9] ]]; then
errors+=("a digit")
fi
# Special character check
if ! [[ "$password" =~ [[:punct:]] ]]; then
errors+=("a special character")
fi
if [ ${#errors[@]} -gt 0 ]; then
echo -e "${RED}Error: Password must contain:${NC}"
printf " - %s\n" "${errors[@]}"
return 1
fi
return 0
}
# Main registration form
echo "=== User Registration ==="
echo
errors=()
# Get and validate username
while true; do
read -p "Username: " username
if validate_required "$username" "Username" && \
validate_length "$username" 3 20 "Username" && \
validate_pattern "$username" '^[a-zA-Z0-9_]+$' "Username (alphanumeric and underscore only)"; then
break
fi
echo
done
# Get and validate email
while true; do
read -p "Email: " email
if validate_required "$email" "Email" && \
validate_email "$email"; then
break
fi
echo
done
# Get and validate phone
while true; do
read -p "Phone: " phone
if validate_required "$phone" "Phone" && \
validate_phone "$phone"; then
break
fi
echo
done
# Get and validate age
while true; do
read -p "Age: " age
if validate_required "$age" "Age" && \
validate_pattern "$age" '^[0-9]+$' "Age (numbers only)" && \
validate_range "$age" 18 120 "Age"; then
break
fi
echo
done
# Get and validate password
while true; do
read -sp "Password: " password
echo
read -sp "Confirm password: " password2
echo
if [ "$password" != "$password2" ]; then
echo -e "${RED}Error: Passwords do not match${NC}"
elif validate_password "$password"; then
break
fi
echo
done
# Success message
echo -e "\n${GREEN}✓ Registration successful!${NC}"
echo "Username: $username"
echo "Email: $email"
echo "Phone: $phone"
echo "Age: $age"
File Processing Script
#!/bin/bash
# file_processor.sh - Conditional file processing
set -euo pipefail
# Configuration
INPUT_DIR="${1:-./input}"
OUTPUT_DIR="${2:-./output}"
PROCESSED_DIR="${3:-./processed}"
LOG_FILE="processor.log"
# Create directories if they don't exist
for dir in "$INPUT_DIR" "$OUTPUT_DIR" "$PROCESSED_DIR"; do
if [ ! -d "$dir" ]; then
mkdir -p "$dir"
echo "Created directory: $dir"
fi
done
# Logging function
log() {
local level="$1"
local message="$2"
local timestamp=$(date '+%Y-%m-%d %H:%M:%S')
echo "[$timestamp] [$level] $message" | tee -a "$LOG_FILE"
}
# Check if input directory has files
check_input_files() {
if [ ! -d "$INPUT_DIR" ]; then
log "ERROR" "Input directory does not exist: $INPUT_DIR"
return 1
fi
local file_count=$(find "$INPUT_DIR" -maxdepth 1 -type f | wc -l)
if [ "$file_count" -eq 0 ]; then
log "WARNING" "No files found in input directory"
return 1
fi
log "INFO" "Found $file_count files to process"
return 0
}
# Process a single file
process_file() {
local file="$1"
local filename=$(basename "$file")
local extension="${filename##*.}"
local output_file="$OUTPUT_DIR/$filename"
log "INFO" "Processing: $filename"
# Check file permissions
if [ ! -r "$file" ]; then
log "ERROR" "File not readable: $filename"
return 1
fi
# Check file size
local size=$(stat -c%s "$file" 2>/dev/null || stat -f%z "$file" 2>/dev/null)
if [ "$size" -eq 0 ]; then
log "WARNING" "File is empty: $filename"
# Optionally handle empty files
cp "$file" "$output_file.empty"
return 0
fi
# Process based on file type
case "${extension,,}" in
txt|text)
log "INFO" "Processing text file: $filename"
# Convert to uppercase
tr '[:lower:]' '[:upper:]' < "$file" > "$output_file"
;;
csv)
log "INFO" "Processing CSV file: $filename"
# Add line numbers
awk '{print NR "," $0}' "$file" > "$output_file"
;;
log)
log "INFO" "Processing log file: $filename"
# Extract errors
grep -i "error" "$file" > "$output_file.errors" 2>/dev/null || true
if [ ! -s "$output_file.errors" ]; then
rm -f "$output_file.errors"
log "INFO" "No errors found in $filename"
fi
# Also keep original with timestamp
cp "$file" "$output_file"
;;
sh)
log "INFO" "Processing shell script: $filename"
# Check if executable
if [ -x "$file" ]; then
cp "$file" "$output_file"
chmod +x "$output_file"
else
log "WARNING" "Script not executable: $filename"
cp "$file" "$output_file"
fi
;;
*)
log "INFO" "Unknown file type: $extension - copying as-is"
cp "$file" "$output_file"
;;
esac
# Check if processing succeeded
if [ $? -eq 0 ] && [ -f "$output_file" ]; then
log "INFO" "Successfully processed: $filename"
# Move original to processed directory
mv "$file" "$PROCESSED_DIR/$filename"
return 0
else
log "ERROR" "Failed to process: $filename"
return 1
fi
}
# Generate report
generate_report() {
local report_file="$OUTPUT_DIR/processing_report.txt"
{
echo "=== Processing Report ==="
echo "Date: $(date)"
echo "Input directory: $INPUT_DIR"
echo "Output directory: $OUTPUT_DIR"
echo "Processed directory: $PROCESSED_DIR"
echo "========================="
echo
echo "Processed files:"
find "$PROCESSED_DIR" -type f -printf "%f\n" 2>/dev/null | sort | sed 's/^/ - /'
echo
echo "Output files:"
find "$OUTPUT_DIR" -type f -printf "%f (%s bytes)\n" 2>/dev/null | sort | sed 's/^/ - /'
echo
echo "Processing log:"
tail -20 "$LOG_FILE" 2>/dev/null | sed 's/^/ /'
} > "$report_file"
log "INFO" "Report generated: $report_file"
}
# Main execution
main() {
log "INFO" "=== File Processor Started ==="
if check_input_files; then
local success_count=0
local fail_count=0
# Process each file
while IFS= read -r -d '' file; do
if process_file "$file"; then
((success_count++))
else
((fail_count++))
fi
done < <(find "$INPUT_DIR" -maxdepth 1 -type f -print0)
log "INFO" "Processing complete: $success_count succeeded, $fail_count failed"
# Generate report
generate_report
else
log "WARNING" "No files to process"
fi
log "INFO" "=== File Processor Finished ==="
}
# Run main
main
7. Best Practices and Tips
If Statement Best Practices
#!/bin/bash
# 1. Always quote variables in [ ]
# Good
if [ "$name" = "John" ]; then
echo "Hello John"
fi
# Bad - breaks if name is empty or has spaces
if [ $name = "John" ]; then
echo "Hello John"
fi
# 2. Use [[ ]] for bash scripts
# Better - more features, no word splitting
if [[ "$name" == "John" && -f "$file" ]]; then
echo "John's file exists"
fi
# 3. Use (( )) for arithmetic
# Cleaner
if (( age >= 18 )); then
echo "Adult"
fi
# 4. Use meaningful condition names
# Good
is_root() {
[ "$EUID" -eq 0 ]
}
if is_root; then
echo "Running as root"
fi
# 5. Keep conditions simple
# Instead of complex conditions
if [[ "$file" == *.txt ]] && [ -f "$file" ] && [ -r "$file" ] && [ -s "$file" ]; then
# process file
fi
# Create helper functions
is_valid_text_file() {
[[ "$1" == *.txt ]] && [ -f "$1" ] && [ -r "$1" ] && [ -s "$1" ]
}
if is_valid_text_file "$file"; then
# process file
fi
# 6. Use case statements for multiple conditions
# Instead of multiple if-elif
case "$action" in
start|stop|restart|status)
service "$action" "$service_name"
;;
*)
echo "Unknown action: $action"
;;
esac
# 7. Handle errors early
# Guard pattern
[ -f "$file" ] || { echo "File not found"; exit 1; }
[ -r "$file" ] || { echo "File not readable"; exit 1; }
# Continue with main logic
# 8. Use && and || for simple conditions
[ -d "$dir" ] || mkdir -p "$dir"
[ -f "$file" ] && rm "$file"
# 9. Return meaningful exit codes
check_file() {
if [ ! -f "$1" ]; then
return 1 # File not found
fi
if [ ! -r "$1" ]; then
return 2 # File not readable
fi
return 0 # Success
}
if check_file "$file"; then
echo "File OK"
else
case $? in
1) echo "File not found" ;;
2) echo "File not readable" ;;
esac
fi
# 10. Document complex conditions
# Check if user has required permissions and is in correct group
# and file exists and is within size limits
if [[ "$USER" == "$owner" ]] || [[ " ${groups[*]} " =~ " $required_group " ]]; then
if [ -f "$file" ] && [ -r "$file" ] && [ $(stat -c%s "$file") -lt $MAX_SIZE ]; then
# process file
fi
fi
Common Pitfalls and Solutions
#!/bin/bash # Pitfall 1: Spaces in variable names filename="my file.txt" # Wrong if [ -f $filename ]; then # Expands to [ -f my file.txt ] -> error echo "File exists" fi # Correct if [ -f "$filename" ]; then echo "File exists" fi # Pitfall 2: Empty variables unset var # Wrong if [ $var = "value" ]; then # Expands to [ = "value" ] -> error echo "Match" fi # Correct if [ "$var" = "value" ]; then # Expands to [ "" = "value" ] -> false echo "Match" fi # Better if [[ $var == "value" ]]; then # No quotes needed echo "Match" fi # Pitfall 3: Integer vs string comparison num=10 # Wrong if [ "$num" > 5 ]; then # String comparison, not numeric echo "Greater" fi # Correct if [ "$num" -gt 5 ]; then echo "Greater" fi # Better if (( num > 5 )); then echo "Greater" fi # Pitfall 4: Using -a and -o (deprecated) # Wrong if [ -f "$file" -a -r "$file" ]; then echo "Readable file" fi # Correct if [ -f "$file" ] && [ -r "$file" ]; then echo "Readable file" fi # Pitfall 5: Forgetting semicolon before then # Wrong if [ -f "$file" ] then echo "File exists" fi # Correct if [ -f "$file" ]; then echo "File exists" fi # Pitfall 6: Using = for numeric comparison # Wrong if [ "$a" = "$b" ]; then # String comparison echo "Equal" fi # Correct for numbers if [ "$a" -eq "$b" ]; then echo "Equal" fi # Pitfall 7: Not handling command failures # Wrong if cp file1 file2; then echo "Copy succeeded" fi # cp might fail, but we don't check # Correct if cp file1 file2 2>/dev/null; then echo "Copy succeeded" else echo "Copy failed" >&2 exit 1 fi # Pitfall 8: Using test command with unquoted wildcards # Wrong if [ -f *.txt ]; then # Expands to multiple arguments echo "Text file exists" fi # Correct if ls *.txt &>/dev/null; then echo "Text files exist" fi # Or if find . -maxdepth 1 -name "*.txt" -print -quit | grep -q .; then echo "Text files exist" fi
Performance Considerations
#!/bin/bash
# 1. Use shell builtins instead of external commands
# Slow
if [ "$(echo "$string" | wc -c)" -gt 10 ]; then
echo "Long string"
fi
# Fast
if [ "${#string}" -gt 10 ]; then
echo "Long string"
fi
# 2. Avoid unnecessary subshells
# Slow
if grep -q "pattern" file.txt; then
count=$(grep -c "pattern" file.txt)
echo "Found $count matches"
fi
# Fast
count=$(grep -c "pattern" file.txt)
if [ "$count" -gt 0 ]; then
echo "Found $count matches"
fi
# 3. Use && and || for simple conditions
# Instead of
if [ -d "$dir" ]; then
: # do nothing
else
mkdir "$dir"
fi
# Better
[ -d "$dir" ] || mkdir "$dir"
# 4. Use case instead of multiple if statements
# Slow
if [ "$var" = "a" ]; then
process_a
elif [ "$var" = "b" ]; then
process_b
elif [ "$var" = "c" ]; then
process_c
fi
# Fast
case "$var" in
a) process_a ;;
b) process_b ;;
c) process_c ;;
esac
# 5. Group conditions to avoid multiple evaluations
# Slow
if [ -f "$file" ]; then
if [ -r "$file" ]; then
if [ -s "$file" ]; then
process_file "$file"
fi
fi
fi
# Fast
if [ -f "$file" ] && [ -r "$file" ] && [ -s "$file" ]; then
process_file "$file"
fi
# 6. Use early returns to avoid deep nesting
# Bad
process() {
if [ -f "$file" ]; then
if [ -r "$file" ]; then
# process
fi
fi
}
# Good
process() {
[ -f "$file" ] || return 1
[ -r "$file" ] || return 2
# process
return 0
}
Conclusion
The if...else statement is a fundamental building block of Bash scripting:
Key Takeaways
- Test Conditions: Use
[ ]for POSIX compliance,[[ ]]for bash-specific features - File Tests: Check file existence, type, and permissions
- String Tests: Compare and check strings
- Numeric Tests: Use
-eq,-ne,-lt, etc., or(( ))for arithmetic - Logical Operators: Combine conditions with
&&,||, and! - Nested Conditions: Handle complex decision trees
- Error Handling: Always check return values and handle errors
Common Patterns Cheat Sheet
| Pattern | Purpose | Example |
|---|---|---|
if [ -f "$file" ] | Check file exists | if [ -f "/etc/passwd" ] |
if [ -z "$var" ] | Check if variable is empty | if [ -z "$name" ] |
if [ "$a" -eq "$b" ] | Numeric equality | if [ "$age" -eq 18 ] |
if [[ "$str" == *pattern* ]] | Pattern matching | if [[ "$file" == *.txt ]] |
if (( a > b )) | Arithmetic comparison | if (( score >= 60 )) |
if command; then | Command success check | if grep -q "error" log; then |
[ -d "$dir" ] || mkdir "$dir" | Create if not exists | [ -d "$dir" ] || mkdir -p "$dir" |
[ -f "$file" ] && rm "$file" | Remove if exists | [ -f "$temp" ] && rm "$temp" |
Best Practices Summary
- Always quote variables in
[ ]tests - Use
[[ ]]for bash scripts (more features, safer) - Use
(( ))for arithmetic comparisons - Keep conditions simple and use helper functions
- Handle errors early with guard clauses
- Use meaningful variable names for conditions
- Document complex logic with comments
- Test edge cases (empty variables, missing files, etc.)
- Return meaningful exit codes from functions
- Use case statements for multiple condition checks
Mastering conditional statements is essential for writing robust and maintainable Bash scripts. Practice with different scenarios to become proficient in choosing the right condition for each situation.