# Exercise: A Recipe Box App

Finish recipes, a whole command-line application in bash, with add and list subcommands, getopts options, a help page and a version, its recipes in ~/.local/share and its settings in ~/.config made on the first run, columns, and color only on a terminal, while a check script runs it job by job.

Freelancer · Unix · Linux · Updated November 10, 2026

Every family has a few recipes everyone asks for, scattered across sticky notes, screenshots and the backs of envelopes. Here you finish `recipes`, an app that keeps them in one place and answers in a second: `recipes add 25 dinner "Taco Tuesday"` saves one, and `recipes list -q -c dinner` shows the quick dinners in tidy columns. It has everything this chapter covered: options, subcommands, a help page and a version, data and settings where Linux apps keep them, and color only where a person sees it. It's the smaller cousin of `spend`, the expense tracker you build after this module, so everything you write here carries over. A check script runs your app for real, job by job, and tells you what works.

## 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**, a small program that runs checks written for bash. 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-a-recipe-box-app](https://github.com/ask-codey/exercise-bash-a-recipe-box-app), 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. On Windows, put it inside Ubuntu, in your Linux home folder, not under `/mnt/c`.

The folder holds:

- `recipes`: the app to finish, with its help page, a `die` function from *Errors a User Can Act On*, and seven jobs, each a comment saying what to do and exactly what to print.
- `sample-recipes.txt`: eight made-up recipes to try it on.
- `check.bats`: the check. You don't need to change it.

## What to do

Open a terminal and `cd` into the exercise folder. Let the app run with `chmod +x recipes`, then open it with `nano recipes`. Each job is a comment, with room under it for your lines:

```text
# 4. recipes add MINUTES CATEGORY "NAME"
#    - Not exactly three arguments: die with
#      usage: recipes add MINUTES CATEGORY "NAME"
```

1. **Where the recipes live**: `recipes.txt` in `${XDG_DATA_HOME:-$HOME/.local/share}/recipes`, made on the first run (*Where an App Keeps Its Data*).
2. **The settings file**: `config` in `${XDG_CONFIG_HOME:-$HOME/.config}/recipes`, written on the first run with `quick_minutes=30`, then loaded with `source`.
3. **Colors, only on a terminal**: `tput` when `[[ -t 1 ]]` is true, empty text otherwise (*Friendly Output: Columns and Colors*).
4. **`recipes add`**: three words and whole-number minutes, or a clear message; then save the line and say `Added NAME.` in green (*Subcommands: app add, app list*).
5. **`recipes list` options**: `-c CATEGORY` and `-q` with `getopts`, and a message for a wrong option or a missing value (*Options and Flags: getopts and --help*).
6. **`recipes list` output**: a message for an empty box, or a bold header and the recipes in columns.
7. **The commands**: `--help` and `-h`, `--version`, nothing at all, and an unknown word. `add` and `list` are already in the `case`.

Each job's comment gives its message word for word. To try your app without touching your real `~/.local/share` and `~/.config`, give this terminal window a pretend home inside the exercise folder (it works once jobs 1 and 2 use the `XDG_` variables):

```bash
export XDG_DATA_HOME="$PWD/pretend-home/data" XDG_CONFIG_HOME="$PWD/pretend-home/config"
```

## 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, what your app printed, and what to look at, for example *A name with spaces and no quotes arrives as two words, so add gets four. Check $# with -ne 3, and die with the usage line.* The check gives your app a pretend home of its own for every test, so your files are never touched.

## Hints

- **Work from job 1 down.** The script runs from the top, and the `case` at the bottom picks the job, as in *Subcommands: app add, app list*.
- **`$HOME`, not `~`**, inside the quotes: a `~` in quotes stays a plain `~`.
- **A whole number** is a word made only of digits. `case "$1" in "" | *[!0-9]*)` matches an empty word or one with anything else in it.
- **Start getopts' letters with a colon**, `":c:q"`, so `:` and `\?` reach your `case`, and answer them with `die`.
- **Compare with `"$quick_minutes"`**, not 30, so the settings file decides what's quick.
- **One `printf` pattern for the header and the rows**: `'%-22s %7s  %s\n'`. The header puts `"$bold"` before it and `"$reset"` after it.

## When you're done

Every check passes:

```text
check.bats
 ✓ the app is here and bash can read it
 ✓ 1) the first run makes ~/.local/share/recipes/recipes.txt
 ✓ 1) a person's own XDG_DATA_HOME is used
 ✓ 1) the first run doesn't empty a recipe box that has recipes
 ✓ 2) the first run writes the settings file
 ✓ 2) the settings file's quick_minutes is used by list -q
 ✓ 3) no color codes when the output goes to a file or a pipe
 ✓ 3) color on a terminal
 ✓ 4) add saves a recipe and says so
 ✓ 4) add keeps the recipes already there
 ✓ 4) add with the wrong number of words says how to use it
 ✓ 4) add refuses minutes that aren't a whole number
 ✓ 5) list -c keeps one category
 ✓ 5) list -q keeps the quick ones
 ✓ 5) the options work in any order, and joined
 ✓ 5) an unknown option says so
 ✓ 5) -c with no value says so
 ✓ 6) an empty recipe box says how to add one
 ✓ 6) list shows a header and the recipes in columns
 ✓ 6) a recipe added with add is listed
 ✓ 7) --help and -h show the help page
 ✓ 7) --version shows the version
 ✓ 7) nothing after the name shows the help page as an error
 ✓ 7) an unknown command says so

24 tests, 0 failures
```

On a Mac, the check `3) color on a terminal` says it was skipped: it needs Linux's `script` command to pretend to be a terminal.

And by hand, with the pretend home set, your app answers like this (the header is bold and "Added" is green on your screen):

```text
$ ./recipes add 25 dinner "Taco Tuesday"
Added Taco Tuesday.
$ cp sample-recipes.txt pretend-home/data/recipes/recipes.txt
$ ./recipes list -q -c dinner
NAME                   MINUTES  CATEGORY
Taco Tuesday                25  dinner
Lemon Garlic Pasta          30  dinner
$ ./recipes add ten lunch Soup
recipes: minutes must be a whole number, such as 25
$ ./recipes cook
recipes: there's no cook command. Try: recipes --help
```

Now think about `spend`. It adds money, not minutes, and a report of totals per category. Which of your seven jobs could you copy into it almost as they are, and which would need to change?

## 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-a-recipe-box-app`), 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-a-recipe-box-app.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-a-recipe-box-app.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-a-recipe-box-app`, 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 a real command-line application in bash: commands, options, a help page, data that stays put, settings people can change, and output that's pleasant on the screen and clean in a pipe. It's a script file, and it's an application, the idea this course has been building to since Module 5.

## Key terms

- [Subcommand](https://www.ask-codey.com/glossary/subcommand)
- [getopts](https://www.ask-codey.com/glossary/getopts)
- [Here Document](https://www.ask-codey.com/glossary/here-document)
- [XDG Base Directory](https://www.ask-codey.com/glossary/xdg-base-directory)
- [Configuration File](https://www.ask-codey.com/glossary/configuration-file)
- [printf](https://www.ask-codey.com/glossary/printf)
- [tput](https://www.ask-codey.com/glossary/tput)
- [Exit Code](https://www.ask-codey.com/glossary/exit-code)

## Related

- [Options and Flags: getopts and --help](https://www.ask-codey.com/lessons/bash-options-with-getopts)
- [Subcommands: app add, app list](https://www.ask-codey.com/lessons/bash-subcommands)
- [Where an App Keeps Its Data](https://www.ask-codey.com/lessons/bash-app-data-and-config)
- [Friendly Output: Columns and Colors](https://www.ask-codey.com/lessons/bash-friendly-output)

Source: https://www.ask-codey.com/lessons/exercise-bash-a-recipe-box-app
