🎁 New User? Get 20% off your first purchase with code NEWUSER20 Register Now →
Menu

Categories

Bash Scripting Essentials: From Basics to Advanced Automation

Bash Scripting Essentials: From Basics to Advanced Automation

Introduction to Bash Scripting

Bash (Bourne Again Shell) is the default shell on most Linux distributions and an essential skill for anyone working with Unix-like systems. This comprehensive guide takes you from basic concepts to advanced automation techniques, equipping you to write robust, maintainable scripts.

Your First Bash Script

Every Bash script starts with a shebang and should be made executable:

#!/bin/bash
# This is a comment
echo "Hello, World!"

# Make it executable
chmod +x script.sh
./script.sh

The shebang (#!/bin/bash) tells the system which interpreter to use. Always include it for portability.

Variables and Data Types

Bash variables are untyped and do not require declaration:

#!/bin/bash

# Variable assignment (no spaces around =)
name="Linux Admin"
count=42
today=$(date +%Y-%m-%d)

# Variable usage
echo "Hello, $name"
echo "Today is $today"
echo "Count: ${count}"

# Read-only variables
readonly VERSION="1.0.0"

# Environment variables
export PATH="$PATH:/opt/scripts"

# Special variables
echo "Script name: $0"
echo "First argument: $1"
echo "All arguments: $@"
echo "Number of arguments: $#"
echo "Exit status of last command: $?"
echo "Process ID: $$"

Conditionals and Control Flow

Bash provides flexible conditional statements:

#!/bin/bash

# If-else statement
if [ -f "/etc/passwd" ]; then
    echo "Password file exists"
elif [ -d "/etc" ]; then
    echo "At least /etc exists"
else
    echo "Something is very wrong"
fi

# Using [[ ]] for advanced tests (recommended)
file="/var/log/syslog"
if [[ -f "$file" && -r "$file" ]]; then
    echo "File exists and is readable"
fi

# String comparisons
if [[ "$USER" == "root" ]]; then
    echo "Running as root"
fi

# Numeric comparisons
count=10
if [[ $count -gt 5 ]]; then
    echo "Count is greater than 5"
fi

# Case statement
case "$1" in
    start)
        echo "Starting service..."
        ;;
    stop)
        echo "Stopping service..."
        ;;
    restart)
        echo "Restarting service..."
        ;;
    *)
        echo "Usage: $0 {start|stop|restart}"
        exit 1
        ;;
esac

Loops and Iteration

Iterate over data with various loop constructs:

#!/bin/bash

# For loop with list
for server in web1 web2 db1 db2; do
    echo "Checking $server..."
    ping -c 1 "$server" > /dev/null && echo "  UP" || echo "  DOWN"
done

# For loop with range
for i in {1..10}; do
    echo "Iteration $i"
done

# C-style for loop
for ((i=0; i<5; i++)); do
    echo "Index: $i"
done

# Loop over files
for file in /var/log/*.log; do
    echo "Log file: $(basename "$file")"
done

# While loop
counter=0
while [[ $counter -lt 5 ]]; do
    echo "Counter: $counter"
    ((counter++))
done

# Read file line by line
while IFS= read -r line; do
    echo "Line: $line"
done < /etc/hosts

# Until loop
until ping -c 1 google.com > /dev/null 2>&1; do
    echo "Waiting for network..."
    sleep 5
done
echo "Network is up!"

Functions

Organize code into reusable functions:

#!/bin/bash

# Function definition
greet() {
    local name="$1"  # Local variable
    echo "Hello, $name!"
}

# Function with return value
is_root() {
    [[ $EUID -eq 0 ]]
}

# Function with output capture
get_ip() {
    hostname -I | awk '{print $1}'
}

# Usage
greet "World"

if is_root; then
    echo "Running as root"
else
    echo "Running as regular user"
fi

ip_address=$(get_ip)
echo "IP Address: $ip_address"

# Function with multiple parameters
log_message() {
    local level="$1"
    local message="$2"
    echo "[$(date +'%Y-%m-%d %H:%M:%S')] [$level] $message"
}

log_message "INFO" "Script started"
log_message "ERROR" "Something went wrong"

Error Handling

Robust scripts handle errors gracefully:

#!/bin/bash

# Exit on error
set -e

# Exit on undefined variable
set -u

# Pipe failure handling
set -o pipefail

# Combined: set -euo pipefail

# Trap for cleanup
cleanup() {
    echo "Cleaning up..."
    rm -f /tmp/script_temp_*
}
trap cleanup EXIT

# Error handling function
die() {
    echo "ERROR: $1" >&2
    exit "${2:-1}"
}

# Usage with error checking
config_file="/etc/myapp/config"
[[ -f "$config_file" ]] || die "Config file not found: $config_file" 2

# Try-catch pattern
if ! output=$(some_command 2>&1); then
    echo "Command failed: $output" >&2
    exit 1
fi

Working with Input and Output

Handle user input and file operations:

#!/bin/bash

# Read user input
read -p "Enter your name: " username
echo "Hello, $username"

# Read with timeout
if read -t 10 -p "Quick! Enter password: " -s password; then
    echo -e "\nPassword received"
else
    echo -e "\nTimeout!"
fi

# Redirect output
echo "Log entry" >> /var/log/myapp.log

# Redirect stderr
command 2> errors.log

# Redirect both stdout and stderr
command &> all_output.log

# Here document
cat < /tmp/config
server=localhost
port=8080
debug=true
EOF

# Process substitution
diff <(ls /dir1) <(ls /dir2)

Arrays

Work with indexed and associative arrays:

#!/bin/bash

# Indexed array
servers=("web1" "web2" "db1" "db2")

echo "First server: ${servers[0]}"
echo "All servers: ${servers[@]}"
echo "Number of servers: ${#servers[@]}"

# Add element
servers+=("cache1")

# Loop over array
for server in "${servers[@]}"; do
    echo "Server: $server"
done

# Associative array (Bash 4+)
declare -A config
config[host]="localhost"
config[port]="3306"
config[user]="admin"

echo "Database host: ${config[host]}"

# Loop over associative array
for key in "${!config[@]}"; do
    echo "$key = ${config[$key]}"
done

Practical Script: Log Rotation

A complete example for log file management:

#!/bin/bash
set -euo pipefail

# Configuration
LOG_DIR="/var/log/myapp"
MAX_SIZE=$((10 * 1024 * 1024))  # 10MB
KEEP_DAYS=30

log() {
    echo "[$(date +'%Y-%m-%d %H:%M:%S')] $1"
}

rotate_log() {
    local file="$1"
    local timestamp=$(date +'%Y%m%d_%H%M%S')
    local rotated="${file}.${timestamp}"
    
    mv "$file" "$rotated"
    gzip "$rotated"
    touch "$file"
    
    log "Rotated: $(basename "$file")"
}

cleanup_old() {
    find "$LOG_DIR" -name "*.gz" -mtime +$KEEP_DAYS -delete
    log "Cleaned up logs older than $KEEP_DAYS days"
}

main() {
    log "Starting log rotation"
    
    for logfile in "$LOG_DIR"/*.log; do
        [[ -f "$logfile" ]] || continue
        
        size=$(stat -f%z "$logfile" 2>/dev/null || stat -c%s "$logfile")
        if [[ $size -gt $MAX_SIZE ]]; then
            rotate_log "$logfile"
        fi
    done
    
    cleanup_old
    log "Log rotation complete"
}

main "$@"

Best Practices

  1. Always quote variables: Use "$variable" to prevent word splitting
  2. Use [[ ]] over [ ]: More features and safer syntax
  3. Enable strict mode: set -euo pipefail at script start
  4. Use functions: Break complex scripts into manageable pieces
  5. Add help messages: Include --help option for usability
  6. Use ShellCheck: Lint your scripts for common mistakes

Conclusion

Bash scripting is a powerful skill that enables automation of virtually any task on Linux systems. From simple one-liners to complex automation frameworks, mastering Bash opens up endless possibilities for system administration and DevOps work. Practice regularly, read other scripts, and always strive for clean, maintainable code.

Want to take your shell scripting to the next level? Browse our collection of Linux and Bash programming books for in-depth learning resources.

Share this article:

Stay Updated

Subscribe to our newsletter for the latest tutorials, tips, and exclusive offers.