# Exercise: Back Up While You Sleep

Finish a nightly backup that cron can run while nobody is there: rsync mirrors a school-work folder, the script works from any folder with a PATH of its own, logs everything with the date and time, takes a lock with flock, refuses to mirror an empty folder and can rehearse with --dry-run; then write its crontab line for 18:00, while a check runs it the way cron would.

Journeyman · Unix · Linux · Updated November 10, 2026

Sam's school work lives in one folder: essays, reading lists, history notes and a science lab report. Sam wants a copy made every evening at 18:00, without having to remember, and wants to know the next morning that it worked. You'll finish `nightly-backup.sh`, a backup cron can run while nobody is at the keyboard, and write the crontab line that runs it. A check script runs your script the way cron would, from the `/` folder with cron's short PATH, and tells you which jobs are done.

## Before you start

You need a terminal: the Terminal app on a Mac, or Ubuntu on Linux or in WSL on Windows. You also need three tools: **bats** runs the check, **rsync** makes the backup (*Backups That Work: rsync*) and **flock** takes the lock (*Scripts That Run Without You*). If `bats --version` or `rsync --version` doesn't print a version, or `command -v flock` prints nothing, install what's missing:

```bash
sudo apt install bats rsync          # Ubuntu, and Ubuntu in WSL (flock is already there)
brew install bats-core rsync flock   # a Mac, with Homebrew
```

`sudo` asks for your password, the one you use to sign in; nothing shows on the screen while you type it.

## Get the exercise code

Open this lesson's GitHub repository, [github.com/ask-codey/exercise-bash-back-up-while-you-sleep](https://github.com/ask-codey/exercise-bash-back-up-while-you-sleep), also linked from the **Exercise** box beside the lesson. Click **Code**, then **Download ZIP**, and unzip it into your `terminal-practice` folder. Or clone it with Git.

The folder holds:

- `nightly-backup.sh`: the backup to finish, with seven jobs, each a comment saying what to do and exactly what to log.
- `crontab.txt`: the crontab line to write, job 8.
- `school-work/`: practice files to back up.
- `check.bats`: the check. You don't need to change it.

## What to do

`cd` into the exercise folder, run `chmod +x nightly-backup.sh`, and open it with `nano nightly-backup.sh`. Each job is a comment, with room under it for your lines. One line is already written for you: `here=` works out the script's own folder as a full path, wherever it's run from. Use `"$here/school-work/"`, `"$here/backup/"` and `"$here/logs/..."` for every file and folder.

1. **A PATH of its own**, set before the `here=` line (*Scripts That Run Without You*).
2. **Everything into the log**: make `logs/`, then `exec >>` into `logs/backup.log`, errors included.
3. **A `log` function** that puts the date and time in front of each line.
4. **One copy at a time** with `exec 9>` and `flock -n 9`; a second copy logs that it's skipping and stops with exit code 0.
5. **Never mirror nothing**: stop with exit code 1 when `school-work` is missing or empty, before rsync can empty the backup (*Backups That Work: rsync*).
6. **`--dry-run`**: add `-n` to rsync's options, and say "Dry run" in the messages.
7. **The mirror**: `rsync -av --delete` from `school-work/` into `backup/`, with "Backup finished" or "Backup failed" logged after it.
8. **`crontab.txt`**: the line that runs the script at 18:00 every day, by its full path (*Scheduled Jobs: cron*). `pwd` in the exercise folder shows the start of the path.

Each job's comment gives its message word for word. Try your script as you go, and read the log each time:

```bash
./nightly-backup.sh --dry-run
./nightly-backup.sh
cat logs/backup.log
```

## Check your work

From the exercise folder:

```bash
bats check.bats
```

At first most checks fail. That's expected: each job you finish turns its checks into a ✓. A failing check says what it expected and what to look at, for example *backup/ should hold exactly what school-work/ holds, run from the / folder as cron does.* The check runs a copy of your script in a throwaway folder of its own, with made-up school work, so your practice files, your backup and your log are never touched.

## Hints

- **Nothing appears on the screen?** That's job 2 working. Everything is in `logs/backup.log`.
- **Work in order.** Jobs 2 to 4 must come before anything that logs, and job 5 before rsync.
- **An empty folder** prints nothing with `ls -A`, so `[[ -z "$(ls -A "$here/school-work")" ]]` passes when it's empty.
- **rsync's options** fit in an array: `options=(-av --delete)`, then `options+=(-n)` for a dry run, then `rsync "${options[@]}" "$here/school-work/" "$here/backup/"`.
- **Put rsync itself after `if`**, as in `backup.sh`, so the log tells the truth.
- **Try it the cron way** with `as-cron.sh` from *Scripts That Run Without You*, giving it the script's full path.
- **On a Mac**, `crontab.txt`'s line starts with Homebrew's bash: `0 18 * * * /opt/homebrew/bin/bash /Users/your-name/.../nightly-backup.sh`.

## When you're done

Every check passes:

```text
check.bats
 ✓ the script is here and bash can read it
 ✓ rsync and flock are installed
 ✓ 1) it finds its commands with a PATH of its own
 ✓ 2) everything goes into logs/backup.log, and nothing to the screen
 ✓ 3) each log line starts with the date and the time
 ✓ 4) a second copy finds the lock taken, and skips
 ✓ 5) a missing school-work folder: it says so and backs up nothing
 ✓ 5) an empty school-work folder: it says so and keeps the backup
 ✓ 6) --dry-run changes nothing
 ✓ 6) --dry-run lists what would be deleted
 ✓ 7) run the cron way, it mirrors school-work into backup
 ✓ 7) a second run copies only what changed
 ✓ 7) a file deleted from school-work is deleted from the backup
 ✓ 7) a backup that fails says so
 ✓ 8) crontab.txt runs the script at 18:00 every day, by its full path

15 tests, 0 failures
```

Run it twice by hand, and the second run's lines in the log show that nothing needed copying (your dates and times will differ):

```text
2026-11-09 07:41:30 Backup started
sending incremental file list

sent 241 bytes  received 22 bytes  526.00 bytes/sec
total size is 112  speedup is 0.43
2026-11-09 07:41:30 Backup finished
```

Then do a restore test: `rsync -a backup/ restore-test/`, then `diff -r school-work restore-test && echo "The restore matches."`. If you'd like the backup to run every evening for real, add your line from `crontab.txt` with `crontab -e`; that changes your computer until you delete the line again.

Now imagine the backup has run every evening for a year. Which line of your script would you be most glad of on the morning the laptop won't start, and which on the evening a USB stick wasn't plugged in?

## Submit your exercise

The exercise is how you finish this lesson. When your work is done, put it on GitHub in a repo of your own, then submit that repo's address here.

1. On GitHub, click **+**, then **New repository**. Name it (for example `exercise-bash-back-up-while-you-sleep`), choose **Public**, and click **Create repository**. Leave out the README: your folder already has one.
2. In a terminal in your exercise folder (Ubuntu in WSL on Windows), send your work to your new repo. If you downloaded the ZIP:

   ```bash
   git init -b main
   git add .
   git commit -m "My solution"
   git remote add origin https://github.com/your-name/exercise-bash-back-up-while-you-sleep.git
   git push -u origin main
   ```

   If you cloned the starter instead, point your copy at your own repo, then commit and push:

   ```bash
   git remote set-url origin https://github.com/your-name/exercise-bash-back-up-while-you-sleep.git
   git add .
   git commit -m "My solution"
   git push -u origin main
   ```

3. Paste your repo's address, such as `https://github.com/your-name/exercise-bash-back-up-while-you-sleep`, into **Submit your exercise** on this page. The site checks that the repo is public and holds your commits, and then the lesson is complete.

Changed something after you submitted? Push it, and submit again: your latest submission is the one that counts.

## So what

You've built the kind of job administrators leave running on servers for years: it runs on time without anyone there, finds everything it needs, never runs twice at once, refuses to do damage, and leaves a diary that says exactly what happened.

## Key terms

- [cron](https://www.ask-codey.com/glossary/cron)
- [Crontab](https://www.ask-codey.com/glossary/crontab)
- [Cron Expression](https://www.ask-codey.com/glossary/cron-expression)
- [rsync](https://www.ask-codey.com/glossary/rsync)
- [flock](https://www.ask-codey.com/glossary/flock)
- [Log File](https://www.ask-codey.com/glossary/log-file)
- [Absolute Path](https://www.ask-codey.com/glossary/absolute-path)
- [Dry run](https://www.ask-codey.com/glossary/dry-run)

## Related

- [Scheduled Jobs: cron](https://www.ask-codey.com/lessons/bash-cron)
- [Scripts That Run Without You](https://www.ask-codey.com/lessons/bash-unattended-scripts)
- [Backups That Work: rsync](https://www.ask-codey.com/lessons/bash-rsync)

Source: https://www.ask-codey.com/lessons/exercise-bash-back-up-while-you-sleep
