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
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:
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, 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, adiefunction 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:
# 4. recipes add MINUTES CATEGORY "NAME"
# - Not exactly three arguments: die with
# usage: recipes add MINUTES CATEGORY "NAME"
- Where the recipes live:
recipes.txtin${XDG_DATA_HOME:-$HOME/.local/share}/recipes, made on the first run (Where an App Keeps Its Data). - The settings file:
configin${XDG_CONFIG_HOME:-$HOME/.config}/recipes, written on the first run withquick_minutes=30, then loaded withsource. - Colors, only on a terminal:
tputwhen[[ -t 1 ]]is true, empty text otherwise (Friendly Output: Columns and Colors). recipes add: three words and whole-number minutes, or a clear message; then save the line and sayAdded NAME.in green (Subcommands: app add, app list).recipes listoptions:-c CATEGORYand-qwithgetopts, and a message for a wrong option or a missing value (Options and Flags: getopts and --help).recipes listoutput: a message for an empty box, or a bold header and the recipes in columns.- The commands:
--helpand-h,--version, nothing at all, and an unknown word.addandlistare already in thecase.
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):
export XDG_DATA_HOME="$PWD/pretend-home/data" XDG_CONFIG_HOME="$PWD/pretend-home/config"
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, 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
caseat 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 yourcase, and answer them withdie. - Compare with
"$quick_minutes", not 30, so the settings file decides what's quick. - One
printfpattern 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:
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):
$ ./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.
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.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-a-recipe-box-app.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-a-recipe-box-app.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-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.