Skip to content
DevOps devops shell 6 min read

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:

OptionWhat it doesWhy it matters
set -eExit immediately if any command fails (returns a non-zero exit code).Stops the script before a small failure becomes a big mess.
set -uTreat use of an unset variable as an error.Catches typos in variable names instead of silently using "".
set -o pipefailA 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 pipefail in 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 bash and set -euo pipefail so 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 1 with 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 -delete or rm.
Last updated June 15, 2026
Was this helpful?