Shell Scripting Guide

Automate anything on Linux by writing powerful Bash scripts. From simple one-liners to complex automation workflows.

Your First Shell Script

A shell script is simply a text file containing a sequence of commands that the shell executes in order. Every script starts with a shebang line that tells the system which interpreter to use.

#!/bin/bash

# This is a comment
echo "Hello from LinuxLand!"
echo "Today is $(date +%A), $(date +%B) $(date +%d), $(date +%Y)"
echo "You are logged in as: $(whoami)"

To create and run this script:

# Create the script file
nano hello.sh

# Make it executable
chmod +x hello.sh

# Run it
./hello.sh

Variables

Variables store data that your script can reference and manipulate. In Bash, you assign variables without spaces around the equals sign and reference them with a dollar sign.

#!/bin/bash

# Variable assignment (no spaces around =)
NAME="LinuxLand"
VERSION=2026
GREETING="Welcome to $NAME version $VERSION"

echo "$GREETING"

# Command substitution
CURRENT_DIR=$(pwd)
FILE_COUNT=$(ls | wc -l)

echo "Current directory: $CURRENT_DIR"
echo "Files in directory: $FILE_COUNT"

# Read user input
read -p "Enter your name: " USER_NAME
echo "Hello, $USER_NAME!"
Tip: Always quote your variables with double quotes ("$VAR") to prevent word splitting and globbing issues, especially when variables might contain spaces.

Conditionals

Conditionals let your script make decisions and execute different code paths based on conditions.

If/Else Statements

#!/bin/bash

FILE="/etc/nginx/nginx.conf"

if [ -f "$FILE" ]; then
  echo "Nginx configuration found."
elif [ -f "/etc/apache2/apache2.conf" ]; then
  echo "Apache configuration found."
else
  echo "No web server configuration found."
fi

Common Test Operators

# File tests
[ -f file ]    # File exists and is a regular file
[ -d dir ]     # Directory exists
[ -r file ]    # File is readable
[ -w file ]    # File is writable
[ -x file ]    # File is executable

# String comparisons
[ "$a" = "$b" ]    # Strings are equal
[ "$a" != "$b" ]   # Strings are not equal
[ -z "$a" ]        # String is empty

# Numeric comparisons
[ "$a" -eq "$b" ]  # Equal
[ "$a" -gt "$b" ]  # Greater than
[ "$a" -lt "$b" ]  # Less than

Loops

For Loops

#!/bin/bash

# Loop over a list
for distro in Ubuntu Fedora Arch Debian; do
  echo "Distro: $distro"
done

# Loop over files
for file in /var/log/*.log; do
  echo "Log file: $(basename "$file") - Size: $(du -h "$file" | cut -f1)"
done

# C-style for loop
for ((i=1; i<=5; i++)); do
  echo "Iteration $i"
done

While Loops

#!/bin/bash

# Count down
COUNT=5
while [ $COUNT -gt 0 ]; do
  echo "Countdown: $COUNT"
  COUNT=$((COUNT - 1))
  sleep 1
done
echo "Done!"

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

Functions

Functions allow you to group reusable blocks of code, making your scripts cleaner and easier to maintain.

#!/bin/bash

# Define a function
check_service() {
  local SERVICE=$1
  if systemctl is-active --quiet "$SERVICE"; then
    echo "[OK] $SERVICE is running"
    return 0
  else
    echo "[FAIL] $SERVICE is not running"
    return 1
  fi
}

# Use the function
check_service nginx
check_service postgresql
check_service redis

Real-World Script Examples

System Health Check Script

#!/bin/bash

echo "===== System Health Report ====="
echo "Date: $(date)"
echo "Hostname: $(hostname)"
echo ""

echo "--- CPU Load ---"
uptime
echo ""

echo "--- Memory Usage ---"
free -h | head -2
echo ""

echo "--- Disk Usage ---"
df -h / | tail -1 | awk '{print "Used: "$3" / "$2" ("$5" full)"}'
echo ""

# Warn if disk usage exceeds 80%
USAGE=$(df / | tail -1 | awk '{print $5}' | tr -d '%')
if [ "$USAGE" -gt 80 ]; then
  echo "WARNING: Disk usage is above 80%!"
fi

Automated Backup Script

#!/bin/bash

BACKUP_DIR="/backups"
SOURCE="/var/www"
DATE=$(date +%Y%m%d_%H%M%S)
FILENAME="backup_${DATE}.tar.gz"
MAX_BACKUPS=7

# Create backup directory if it does not exist
mkdir -p "$BACKUP_DIR"

# Create the backup
echo "Creating backup: $FILENAME"
tar -czf "${BACKUP_DIR}/${FILENAME}" "$SOURCE" 2>/dev/null

if [ $? -eq 0 ]; then
  echo "Backup created successfully."
  SIZE=$(du -h "${BACKUP_DIR}/${FILENAME}" | cut -f1)
  echo "Size: $SIZE"
else
  echo "ERROR: Backup failed!"
  exit 1
fi

# Remove old backups, keep only the latest
cd "$BACKUP_DIR" || exit
ls -t backup_*.tar.gz | tail -n +$((MAX_BACKUPS + 1)) | xargs -r rm --
echo "Old backups cleaned. Keeping last $MAX_BACKUPS."

Scheduling Scripts with Cron

Cron lets you schedule scripts to run automatically at specific times or intervals.

# Edit your crontab
crontab -e

# Cron format: minute hour day month weekday command
# ┌──── minute (0-59)
# │ ┌──── hour (0-23)
# │ │ ┌──── day of month (1-31)
# │ │ │ ┌──── month (1-12)
# │ │ │ │ ┌──── day of week (0-6, Sunday=0)
# │ │ │ │ │
# * * * * * command

# Run backup daily at 2 AM
0 2 * * * /usr/local/bin/backup.sh

# Run health check every 30 minutes
*/30 * * * * /usr/local/bin/health-check.sh

# Run cleanup every Sunday at midnight
0 0 * * 0 /usr/local/bin/cleanup.sh

Best Practices

  • Always start with #!/bin/bash (or #!/usr/bin/env bash for portability)
  • Use set -euo pipefail at the top to catch errors early and fail on undefined variables
  • Quote all variables to prevent word splitting and unexpected behaviour
  • Use local variables inside functions to avoid polluting the global scope
  • Add error handling for critical operations using if checks or trap
  • Log output to files with timestamps for scripts that run unattended
  • Test scripts with bash -n script.sh (syntax check) and shellcheck script.sh (linting)
#!/bin/bash
set -euo pipefail

# Trap errors and clean up
cleanup() {
  echo "Cleaning up temporary files..."
  rm -f /tmp/myapp_*.tmp
}
trap cleanup EXIT

# Your script logic here
echo "Script running safely with error handling."

Discover essential tools every Linux user needs.

Essential Linux Tools →