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
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:
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, 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.
- A PATH of its own, set before the
here=line (Scripts That Run Without You). - Everything into the log: make
logs/, thenexec >>intologs/backup.log, errors included. - A
logfunction that puts the date and time in front of each line. - One copy at a time with
exec 9>andflock -n 9; a second copy logs that it's skipping and stops with exit code 0. - Never mirror nothing: stop with exit code 1 when
school-workis missing or empty, before rsync can empty the backup (Backups That Work: rsync). --dry-run: add-nto rsync's options, and say "Dry run" in the messages.- The mirror:
rsync -av --deletefromschool-work/intobackup/, with "Backup finished" or "Backup failed" logged after it. crontab.txt: the line that runs the script at 18:00 every day, by its full path (Scheduled Jobs: cron).pwdin 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:
./nightly-backup.sh --dry-run
./nightly-backup.sh
cat logs/backup.log
Check your work
From the exercise folder:
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), thenoptions+=(-n)for a dry run, thenrsync "${options[@]}" "$here/school-work/" "$here/backup/". - Put rsync itself after
if, as inbackup.sh, so the log tells the truth. - Try it the cron way with
as-cron.shfrom 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:
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):
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.
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.In a terminal in your exercise folder (Ubuntu in WSL on Windows), send your work to your new repo. If you downloaded the ZIP:
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 mainIf you cloned the starter instead, point your copy at your own repo, then commit and push:
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 mainPaste 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.