Bash Shell Script Basics for VPS Sysadmins
Shell scripts are the most powerful automation tool on Linux VPS. Repetitive tasks like backups, checking disk space, sending alerts when services stop, or cleaning up log files can all be automated with scripts. This article teaches the Bash scripting fundamentals that sysadmins use most frequently.
Basic Script Structure
#!/bin/bash
# Every script must start with shebang
# Exit immediately if any command fails (best practice)
set -euo pipefail
# Variables
BACKUP_DIR="/backup"
DATE=$(date +%Y%m%d)
LOG_FILE="/var/log/myscript.log"
echo "Script started at $(date)" | tee -a "$LOG_FILE"
Variables and Conditions
# Variables and arguments
SITE_NAME="$1" # Get first argument
DISK_THRESHOLD="${2:-80}" # Default to 80 if not specified
# If/else
DISK_USAGE=$(df / | awk 'NR==2 {print $5}' | tr -d '%')
if [ "$DISK_USAGE" -gt "$DISK_THRESHOLD" ]; then
echo "WARNING: Disk usage at ${DISK_USAGE}%"
# Send alert email
echo "Disk usage: ${DISK_USAGE}%" | mail -s "Disk Alert: $(hostname)" [email protected]
else
echo "Disk OK: ${DISK_USAGE}%"
fi
Loops
# Loop through a list
SITES=("site1.com" "site2.com" "site3.com")
for SITE in "${SITES[@]}"; do
echo "Backing up $SITE..."
mysqldump -u backup_user -p"$DB_PASS" "$SITE" > "/backup/${SITE}_${DATE}.sql"
done
# while loop
MAX_RETRIES=5
COUNT=0
while [ $COUNT -lt $MAX_RETRIES ]; do
if curl -sf "https://example.com" > /dev/null; then
echo "Site is up"
break
fi
COUNT=$((COUNT + 1))
sleep 5
done
Functions
# Declare function
log() {
echo "[$(date '+%Y-%m-%d %H:%M:%S')] $*" | tee -a "$LOG_FILE"
}
check_service() {
local SERVICE="$1"
if systemctl is-active --quiet "$SERVICE"; then
log "$SERVICE is running"
return 0
else
log "ERROR: $SERVICE is not running"
systemctl start "$SERVICE"
return 1
fi
}
# Use function
check_service nginx
check_service mysql
check_service php8.3-fpm
set -euo pipefail at the start of your script: -e exits immediately if a command fails, -u raises an error if you use an undefined variable, and -o pipefail makes a pipe fail if any command in the pipeline fails.
Verify Shell Script is Working Correctly
# Check syntax before running
bash -n myscript.sh
# Run in debug mode (shows every command executed)
bash -x myscript.sh
# Test with shellcheck (lint tool)
sudo apt install shellcheck -y
shellcheck myscript.sh
# Run the script
chmod +x myscript.sh
./myscript.sh
shellcheck is an excellent linter that recommends best practices and catches errors you might miss. Always run it before deploying a script to production.
Common Issues & Troubleshooting
Script fails with "Permission denied"
The file doesn't have execute permission. You need to chmod it first:
chmod +x script.sh
# Or run bash directly (no execute permission needed)
bash script.sh
Script works on terminal but fails in cron
Cron has a limited PATH compared to a normal shell. You must specify the full path of commands:
# Wrong (cron might not find wp in its limited PATH)
wp core update
# Correct
/usr/local/bin/wp core update
# Or set PATH at the start of the script
export PATH="/usr/local/bin:/usr/bin:/bin:$PATH"
Script returns incorrect exit code
You need to return the correct exit code so cron and monitoring tools know whether it succeeded or failed:
# Return specific exit codes
exit 0 # success
exit 1 # failure
# Check the exit code of the previous command
if ! mysqldump ...; then
log "Backup FAILED"
exit 1
fi
log "Backup SUCCESS"
exit 0
Variables with spaces break commands into multiple arguments
Always quote variables when you use them:
# Wrong — if FILE="/path/to/my file.txt"
rm $FILE # becomes rm /path/to/my file.txt
# Correct
rm "$FILE" # works correctly even with spaces
Frequently Asked Questions
What's the difference between Bash and sh?
sh (POSIX shell) is a minimal shell available on every Unix/Linux system, with limited features. Bash has more features like arrays, [[]], and process substitution. For scripts you know will run on servers that already have Bash, you can use Bash directly.
How do I debug a script that fails in production?
Add set -x at the start to trace every command executed. Redirect output to a log file: bash -x script.sh 2>&1 | tee /tmp/debug.log and then examine the log to find the line that failed.
How should a script store passwords?
Never hardcode passwords directly in the script. Use these alternatives instead: 1) Read from environment variables ($DB_PASS) set in sudoer/cron environment, 2) Read from a config file with 600 permissions, or 3) Use a credentials helper like a secret manager.