Creating a Real-World Application
Now that we have learned about variables, unbound variables, parameter expansion, and strict mode, let's combine all these concepts into a practical script. We will create a simple file backup utility that demonstrates best practices for handling variables in Bash.
Planning Our Backup Script
Our backup script will:
- Take a source directory and backup destination as inputs
- Allow configuration through environment variables or command-line arguments
- Handle errors gracefully using strict mode
- Provide useful feedback to the user
Creating the Backup Script
- Create a new file named
backup.sh
with the following content:
#!/bin/bash
## ====================================
## File Backup Utility
## ====================================
## --- Strict mode ---
set -euo pipefail
## --- Script metadata ---
SCRIPT_NAME="$(basename "$0")"
SCRIPT_VERSION="1.0.0"
## --- Default configuration ---
SOURCE_DIR=${SOURCE_DIR:-"$(pwd)"}
BACKUP_DIR=${BACKUP_DIR:-"/tmp/backups"}
BACKUP_NAME=${BACKUP_NAME:-"backup_$(date +%Y%m%d_%H%M%S)"}
EXCLUDE_PATTERN=${EXCLUDE_PATTERN:-"*.tmp"}
VERBOSE=${VERBOSE:-"false"}
## --- Helper functions ---
log() {
if [[ "$VERBOSE" == "true" ]]; then
echo "$(date "+%Y-%m-%d %H:%M:%S") - $1"
fi
}
error() {
echo "ERROR: $1" >&2
exit 1
}
usage() {
cat << EOF
Usage: $SCRIPT_NAME [options]
A simple file backup utility.
Options:
-s, --source DIR Source directory to backup (default: current directory)
-d, --destination DIR Backup destination directory (default: /tmp/backups)
-n, --name NAME Name for the backup archive (default: backup_YYYYMMDD_HHMMSS)
-e, --exclude PATTERN Files to exclude (default: *.tmp)
-v, --verbose Enable verbose output
-h, --help Show this help message and exit
Environment variables:
SOURCE_DIR Same as --source
BACKUP_DIR Same as --destination
BACKUP_NAME Same as --name
EXCLUDE_PATTERN Same as --exclude
VERBOSE Set to 'true' to enable verbose output
EOF
}
## --- Process command line arguments ---
while [[ $## -gt 0 ]]; do
case $1 in
-s | --source)
SOURCE_DIR="$2"
shift 2
;;
-d | --destination)
BACKUP_DIR="$2"
shift 2
;;
-n | --name)
BACKUP_NAME="$2"
shift 2
;;
-e | --exclude)
EXCLUDE_PATTERN="$2"
shift 2
;;
-v | --verbose)
VERBOSE="true"
shift
;;
-h | --help)
usage
exit 0
;;
*)
error "Unknown option: $1"
;;
esac
done
## --- Main function ---
main() {
## Validate source directory
if [[ ! -d "$SOURCE_DIR" ]]; then
error "Source directory does not exist: $SOURCE_DIR"
fi
## Create backup directory if it doesn't exist
if [[ ! -d "$BACKUP_DIR" ]]; then
log "Creating backup directory: $BACKUP_DIR"
mkdir -p "$BACKUP_DIR" || error "Failed to create backup directory"
fi
## Full path for the backup file
BACKUP_FILE="$BACKUP_DIR/$BACKUP_NAME.tar.gz"
log "Starting backup from $SOURCE_DIR to $BACKUP_FILE"
log "Excluding files matching: $EXCLUDE_PATTERN"
## Create the backup
tar -czf "$BACKUP_FILE" \
--exclude="$EXCLUDE_PATTERN" \
-C "$(dirname "$SOURCE_DIR")" "$(basename "$SOURCE_DIR")" \
|| error "Backup failed"
## Check if backup was created
if [[ -f "$BACKUP_FILE" ]]; then
BACKUP_SIZE=$(du -h "$BACKUP_FILE" | cut -f1)
echo "Backup completed successfully: $BACKUP_FILE ($BACKUP_SIZE)"
else
error "Backup file was not created"
fi
}
## --- Run main function ---
main
- Make the script executable:
chmod +x backup.sh
- Run the script with the default settings:
./backup.sh
You should see a message confirming that the backup was created successfully.
- Check the backup file:
ls -lh /tmp/backups/
- Try running the script with different options:
./backup.sh --source ~/project --destination ~/backups --name my_project_backup --verbose
If the ~/backups
directory doesn't exist, the script will create it. You might see an error if you don't have write permissions to create that directory.
Testing Error Handling in Our Backup Script
Let's test how our script handles errors:
- Try to backup a non-existent directory:
./backup.sh --source /path/that/does/not/exist
You should see an error message like:
ERROR: Source directory does not exist: /path/that/does/not/exist
- Try to set an invalid backup destination (where you don't have write permissions):
./backup.sh --destination /root/backups
You should see an error message indicating that the script failed to create the backup directory.
Creating a Test Environment
Let's create a test environment to demonstrate our backup script:
- Create a test directory structure:
mkdir -p ~/project/test_backup/{docs,images,code}
touch ~/project/test_backup/docs/{readme.md,manual.pdf}
touch ~/project/test_backup/images/{logo.png,banner.jpg}
touch ~/project/test_backup/code/{script.sh,data.tmp,config.json}
- Run the backup script on this test directory:
./backup.sh --source ~/project/test_backup --exclude "*.tmp" --verbose
- List the files in the backup:
tar -tvf /tmp/backups/backup_*.tar.gz | sort
You should see all files except those matching the exclude pattern (*.tmp).
This backup script demonstrates all the concepts we've covered:
- Setting default values for variables using parameter expansion
- Using strict mode to catch errors
- Handling command-line arguments and environment variables
- Providing user feedback and error messages
- Validating inputs and handling edge cases
With these techniques, you can write robust Bash scripts that handle unbound variables gracefully and provide a better user experience.