Practical Script Examples
You have learned the building blocks of Bash — variables, loops, conditionals, functions, and exit codes. Now it is time to put them together into scripts you would actually run on a real Ubuntu server. This page gives you three complete, copy-paste-ready scripts: a dated backup script, a disk-space alert, and a service health check. Every line works on a fresh Ubuntu 22.04 or 24.04 LTS (Long-Term Support) machine, with no # your logic here placeholders.
The safety header every script should start with
Before the scripts, one habit. Every serious Bash script should begin with the same line:
#!/usr/bin/env bash
set -euo pipefail
Here is what set -euo pipefail does, term by term:
| Option | What it does | Why it matters |
|---|---|---|
set -e | Exit immediately if any command fails (returns a non-zero exit code). | Stops the script before a small failure becomes a big mess. |
set -u | Treat use of an unset variable as an error. | Catches typos in variable names instead of silently using "". |
set -o pipefail | A pipeline (a | b) fails if any command in it fails, not just the last one. | Without it, grep x file | sort looks “successful” even if grep errored. |
Always use
set -euo pipefailin scripts you schedule with cron or run as root. A script that “keeps going” after an error can delete the wrong files or report success when it actually failed.
Script 1 — A dated backup-to-tar script
This script bundles a directory into a single compressed archive named with today’s date and time, then deletes archives older than a set number of days so your disk does not fill up. A “tar archive” (short for tape archive) is one file that contains many files, and .tar.gz means it is also gzip-compressed (made smaller).
When to use this: nightly backups of a website folder, a database dump directory, or app config. When NOT to use this: for live databases, dump them first (e.g. pg_dump) — never tar a database’s raw data files while it is running, as you may capture a half-written, corrupt copy.
Save this as backup.sh:
#!/usr/bin/env bash
set -euo pipefail
# --- Configuration ---
SOURCE_DIR="/var/www/myapp" # What to back up
BACKUP_DIR="/var/backups/myapp" # Where to store archives
RETENTION_DAYS=7 # Delete archives older than this
# A timestamp like 2026-06-15_0230 (year-month-day_hourminute)
TIMESTAMP="$(date +%Y-%m-%d_%H%M)"
ARCHIVE="${BACKUP_DIR}/myapp_${TIMESTAMP}.tar.gz"
# Make sure the source exists before we do anything
if [[ ! -d "$SOURCE_DIR" ]]; then
echo "ERROR: source directory $SOURCE_DIR does not exist" >&2
exit 1
fi
# Create the backup folder if it is not there yet (-p = no error if it exists)
mkdir -p "$BACKUP_DIR"
echo "Backing up $SOURCE_DIR ..."
# -c create, -z gzip, -f file. -C changes directory so paths inside stay short.
tar -czf "$ARCHIVE" -C "$(dirname "$SOURCE_DIR")" "$(basename "$SOURCE_DIR")"
echo "Created $ARCHIVE"
# Delete archives older than RETENTION_DAYS. -mtime +N means "more than N days old".
echo "Removing backups older than $RETENTION_DAYS days ..."
find "$BACKUP_DIR" -name 'myapp_*.tar.gz' -type f -mtime +"$RETENTION_DAYS" -delete
echo "Backup complete."
Make it executable and run it:
sudo chmod +x backup.sh
sudo ./backup.sh
Output:
Backing up /var/www/myapp ...
Created /var/backups/myapp/myapp_2026-06-15_0230.tar.gz
Removing backups older than 7 days ...
Backup complete.
To run it automatically every night at 2:30 AM, edit the root crontab with sudo crontab -e and add:
30 2 * * * /usr/local/bin/backup.sh >> /var/log/myapp-backup.log 2>&1
The >> ... 2>&1 part appends both normal output and errors to a log file so you can check what happened.
Script 2 — A disk-space alert
If a server’s disk fills to 100%, services crash and logs stop writing. This script checks how full a disk is and warns you when it crosses a threshold. It uses df (disk free) to read usage and prints a clear message — perfect to run from cron and pipe into an email or alert.
When to use this: any always-on server. When NOT to use this: as your only safety net on critical systems — pair it with proper monitoring (e.g. Prometheus) for production.
Save this as disk-alert.sh:
#!/usr/bin/env bash
set -euo pipefail
THRESHOLD=80 # Alert when usage is at or above this percent
MOUNT="/" # The filesystem to check (root partition)
# df -P gives portable output; tail -1 skips the header row.
# awk pulls column 5 (the "Use%" value) and strips the % sign.
USAGE="$(df -P "$MOUNT" | tail -1 | awk '{ gsub(/%/,"",$5); print $5 }')"
echo "Disk usage on $MOUNT is ${USAGE}%."
if (( USAGE >= THRESHOLD )); then
echo "WARNING: disk usage ${USAGE}% has reached the ${THRESHOLD}% threshold!" >&2
# Exit non-zero so cron / a wrapper can detect the alert state.
exit 1
fi
echo "Disk usage is healthy."
Run it:
sudo chmod +x disk-alert.sh
./disk-alert.sh
Output:
Disk usage on / is 64%.
Disk usage is healthy.
The (( ... )) syntax is Bash arithmetic — it compares numbers without needing [ ]. Because the script exits with code 1 when the disk is too full, you can chain it: ./disk-alert.sh || mail -s "Disk alert" [email protected] < /dev/null.
Script 3 — A service health check
This script checks whether a systemd service (a background program managed by Ubuntu’s systemctl) is running, and tries to restart it if it is not. systemd is the standard service manager on Ubuntu, and systemctl is-active tells you a service’s status in one word.
When to use this: a quick self-healing check for a service that occasionally crashes. When NOT to use this: as a replacement for systemd’s own Restart=on-failure — that is the proper, built-in way to auto-restart. Use this script for reporting and for services where you also want a notification.
Save this as healthcheck.sh:
#!/usr/bin/env bash
set -euo pipefail
# Pass the service name as the first argument, default to nginx.
SERVICE="${1:-nginx}"
# is-active prints "active" / "inactive" / "failed". || true stops set -e
# from killing the script when the service is down (a non-zero exit).
STATUS="$(systemctl is-active "$SERVICE" || true)"
echo "Service '$SERVICE' status: $STATUS"
if [[ "$STATUS" == "active" ]]; then
echo "OK: $SERVICE is running."
exit 0
fi
echo "WARNING: $SERVICE is not running. Attempting restart ..." >&2
sudo systemctl restart "$SERVICE"
# Re-check after the restart attempt.
if [[ "$(systemctl is-active "$SERVICE" || true)" == "active" ]]; then
echo "RECOVERED: $SERVICE restarted successfully."
exit 0
else
echo "CRITICAL: $SERVICE failed to restart. Manual action needed." >&2
exit 1
fi
Run it against any service, for example PostgreSQL:
sudo chmod +x healthcheck.sh
./healthcheck.sh postgresql
Output:
Service 'postgresql' status: active
OK: postgresql is running.
The ${1:-nginx} means “use the first argument, or default to nginx if none is given.” This makes the script reusable for any service name.
Best practices
- Start every script with
#!/usr/bin/env bashandset -euo pipefailso failures stop the script instead of corrupting data. - Put configuration (paths, thresholds, retention) in named variables at the top so you never hunt through the body to change a value.
- Quote every variable expansion (
"$VAR") to survive spaces and special characters in filenames. - Validate inputs early — check that source directories exist before acting, and
exit 1with a clear message on>&2(standard error) when they do not. - Make scripts exit non-zero on a problem so cron, CI, and wrapper scripts can detect failure automatically.
- Log scheduled scripts to a file (
>> /var/log/... 2>&1) so you have a record when something breaks at 3 AM. - Test on a throwaway directory first — especially anything that calls
find -deleteorrm.