# Exercise: Make It Fail Well

Turn a photo mover that works only on a good day into one you can trust: strict mode, a die function, a usage message, errors on standard error, exit codes that mean something, cleanup with trap, a bug found with bash -x, and two bats tests of your own, while a check script runs it job by job.

Journeyman · Unix · Linux · Updated November 10, 2026

Sam's new phone and old phone both number their photos from the same place, so every evening `mover.sh` moves the day's photos from `inbox` into `photos` and must never let one replace another. On a good day it works. On a bad day it says the wrong things in the wrong place, reports success when it failed, leaves half-copied files behind, and has a bug that loses a photo that was already there. You'll make it fail well, the way an application people depend on should, and then write two tests of your own. A check script runs your script for real, job by job, and tells you what's done.

## Before you start

You need a terminal: the Terminal app on a Mac, or Ubuntu on Linux or in WSL on Windows. The check runs with **bats**, from *Testing Scripts with bats*. If `bats --version` doesn't print a version number yet, install it:

```bash
sudo apt install bats       # Ubuntu, and Ubuntu in WSL
brew install bats-core      # 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-make-it-fail-well](https://github.com/ask-codey/exercise-bash-make-it-fail-well), 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:

- `mover.sh`: the mover to finish, with eleven jobs, each a comment saying what to do and exactly what to print.
- `my-tests.bats`: one test for the mover, and room for two of yours.
- `inbox` and `photos`: practice photos, each a one-line text file. `photos/IMG_0412.jpg` is a birthday cake that must survive.
- `check.bats`: the check. You don't need to change it.

As it is, the starter can lose the practice photos, for example when it's given a TO folder that doesn't exist. That's what you're fixing; if it happens, download the exercise again.

## What to do

`cd` into the exercise folder, run `chmod +x mover.sh`, and open it with `nano mover.sh`. Each job is a comment, with room under it for your lines:

```text
# 1. Turn on strict mode.


usage="Usage: mover.sh FROM-FOLDER TO-FOLDER"

# 2. Write a die function: it prints "mover: " and its message on standard
#    error, then stops the script with exit code 1.
```

1. **Strict mode**, on its own line (*Strict Mode: set -euo pipefail*).
2. **A `die` function** that prints on standard error and stops with exit code 1 (*Errors a User Can Act On*).
3. **`--help`**: the usage line on standard output, exit code 0.
4. **Run wrong**, with anything but two folders: the usage line on standard error, exit code 2.
5. **Missing folders**: `die` with the job's message for each.
6. **Clean up**: `tmp=""` and a `trap` that removes `"$tmp"`, however the script ends (*Cleaning Up: trap and mktemp*).
7. **The bug**: a photo already in the TO folder gets replaced. Find why with `bash -x` (*Debugging Scripts: bash -x*), and fix it.
8. **Skipped photos**: the warning on standard error, and counted.
9. **A failed copy**: `die` with the job's message, keeping the original.
10. **The summary**: `Done: N moved, M skipped.`, then exit code 1 when anything was skipped.
11. **Your tests**: two more tests in `my-tests.bats` (*Testing Scripts with bats*).

Each job's comment gives its message word for word. Save, and try your script as you go: `./mover.sh`, `./mover.sh inbx photos`, then `./mover.sh inbox photos`.

## 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 check into a ✓. A failing check says what it expected on each stream, what your script printed there, and what to look at, for example *The warning was printed on standard output. Add >&2 to its echo (job 8).* The check runs your script in a throwaway folder of its own, with made-up photos, so the practice photos in your folder are never touched. `bats my-tests.bats` runs your own tests alone.

## Hints

- **Work from the top.** Strict mode first, so every later job is tested the way the finished script runs.
- **Strict mode's surprises** will show up: `"$1"` with no arguments needs `"${1:-}"`, and `((moved++))` stops the script when `moved` is 0, so write `moved=$((moved + 1))`.
- **`|| die`** reads "or else die": `[[ -d "$src" ]] || die "..."`, and the same after the `cp` line.
- **The trap's command goes in single quotes**, above the loop, after `tmp=""`.
- **For job 7**, run `bash -x ./mover.sh inbox photos` on a fresh copy of the practice photos and read the line that starts `+ [[ -e`. Which path did it really check?
- **For job 11**, copy the first test in `my-tests.bats` and change it. A photo that's already there is one `printf` line before `run`.
- If you have ShellCheck, run `shellcheck mover.sh` when you're done, and read anything it has to say.

## When you're done

Every check passes:

```text
check.bats
 ✓ the script is here and bash can read it
 ✓ 1) strict mode is on
 ✓ 3) --help prints the usage line on standard output
 ✓ 4) run with nothing, it prints the usage line on standard error
 ✓ 4) run with one folder, it prints the usage line too
 ✓ 2) 5) a missing FROM folder: die says so
 ✓ 2) 5) a missing TO folder: die says so
 ✓ 7) 8) never replaces a photo that's already there
 ✓ 8) 10) moves every photo and says how many
 ✓ 8) 10) a skipped photo is counted, and the exit code says so
 ✓ 6) 9) a failed copy: it says so and leaves no half-copied file
 ✓ 11) your own tests: at least three, and they pass

12 tests, 0 failures
```

And by hand, in the exercise folder:

```text
$ ./mover.sh
Usage: mover.sh FROM-FOLDER TO-FOLDER
$ ./mover.sh inbx photos
mover: there's no folder called 'inbx'. Check the name with ls.
$ ./mover.sh inbox photos
mover: skipped IMG_0412.jpg: photos already has one.
Moved IMG_0413.jpg.
Moved IMG_0414.jpg.
Moved beach day.jpg.
Done: 3 moved, 1 skipped.
```

Now think about who runs this script at night, when nobody is watching. What does exit code 1 let a nightly job do that the starter's exit code 0 never could?

## 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-make-it-fail-well`), 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-make-it-fail-well.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-make-it-fail-well.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-make-it-fail-well`, 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

Your mover now behaves like software people can depend on: it explains itself, puts problems where they belong, reports failure honestly, tidies up after an interruption, and comes with tests that prove it.

## Key terms

- [Standard Streams](https://www.ask-codey.com/glossary/standard-streams)
- [Exit Code](https://www.ask-codey.com/glossary/exit-code)
- [Usage Message](https://www.ask-codey.com/glossary/usage-message)
- [Strict Mode](https://www.ask-codey.com/glossary/strict-mode-unix)
- [trap](https://www.ask-codey.com/glossary/trap)
- [mktemp](https://www.ask-codey.com/glossary/mktemp)
- [Execution Trace](https://www.ask-codey.com/glossary/execution-trace)
- [bats](https://www.ask-codey.com/glossary/bats)

## Related

- [Errors a User Can Act On](https://www.ask-codey.com/lessons/bash-script-errors)
- [Strict Mode: set -euo pipefail](https://www.ask-codey.com/lessons/bash-strict-mode)
- [Cleaning Up: trap and mktemp](https://www.ask-codey.com/lessons/bash-trap-and-temp-files)
- [Debugging Scripts: bash -x](https://www.ask-codey.com/lessons/bash-debugging-scripts)
- [ShellCheck: A Second Pair of Eyes](https://www.ask-codey.com/lessons/bash-shellcheck)
- [Testing Scripts with bats](https://www.ask-codey.com/lessons/bash-testing-with-bats)

Source: https://www.ask-codey.com/lessons/exercise-bash-make-it-fail-well
