Exercise: Three Things at Once

Write three tools for a day's work on Bluebird Bakery's website: three-panes.sh sets up the server, the tests rerunning in watch and a free terminal in one tmux session, wait-for-site.sh waits for the site and gives up at a time limit, and resize-all.sh resizes photos four at a time, while a check script tells you what's done.

Freelancer · Unix · Linux · Updated

A day's work on Bluebird Bakery's website means three things at once: the site's server running, its tests rerunning every few seconds, and a terminal to type in. There are twelve vacation photos to shrink for the site, too. You'll write one tool for each lesson of this chapter: three-panes.sh, which sets up all three in one tmux session with one command; wait-for-site.sh, which waits until the site answers and gives up at a time limit; and resize-all.sh, which resizes the photos four at a time. A check script 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, and the exercise uses tmux, watch, curl and python3, which runs the practice web server. Check each one with --version (tmux -V for tmux), and install what's missing:

sudo apt install bats tmux curl python3       # Ubuntu, and Ubuntu in WSL
brew install bats-core tmux watch coreutils   # 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. Ubuntu already has watch. A Mac already has curl and python3, and coreutils adds timeout.

Get the exercise code

Open this lesson's GitHub repository, github.com/ask-codey/exercise-bash-three-things-at-once, 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 in a Windows folder under /mnt/c, so chmod works on the scripts.

The folder holds:

  • answers.txt: five questions, each with a numbered line for your answer.
  • three-panes.sh, wait-for-site.sh and resize-all.sh: the three tools. Each starts with a comment that says exactly what it must do and print. These, and answers.txt, are the files you write.
  • site/, start-server.sh and test.sh: the bakery's website, the script that serves it at http://localhost:8000 after a few seconds, and the tests from Waiting for Things: watch, until and timeout.
  • photos/ and resize.sh: twelve pretend photos, a packing list that ended up among them, and the one-second stand-in resizer from Running Work in Parallel: &, wait and xargs -P.
  • check.bats: the check. You don't need to change it.

What to do

Open a terminal in the exercise folder, and make the scripts runnable:

chmod +x *.sh
  1. Answer the questions in answers.txt, trying each one in tmux or the terminal first. Write each answer after its ), like the example: 0) tmux new -s bakery.
  2. Write three-panes.sh. One session named bakery, with three panes: ./start-server.sh, watch -n 5 ./test.sh, and a free terminal. Run it, watch the tests turn from 000 to 200 as the server starts, then detach with Ctrl+B d and run it again: you're back in the same session, not a new one. tmux kill-session -t bakery ends it.
  3. Write wait-for-site.sh URL [SECONDS]. With the server stopped, ./wait-for-site.sh http://localhost:8000/ 5 gives up after five seconds. Start the server in another pane, run it again, and it says the site is up as soon as it answers.
  4. Write resize-all.sh. Four at a time, every photo and nothing else, and it finishes only when the last photo is done. time ./resize-all.sh shows the difference: about three seconds instead of twelve.

Check your work

From the exercise folder:

bats check.bats

At first almost every check fails. That's expected: each question you answer and each tool you finish turns its check into a ✓. The check tries your scripts on copies in a throwaway folder, with a practice website on a free port and a tmux of its own, so it never touches your own sessions or your server. It stops only what it started, and takes about 20 seconds. A failing check says what to look at, for example resize.sh ran one photo at a time, so it took 12 seconds. Run up to 4 at once with xargs -n 1 -P 4.

Hints

  • A session from a script: tmux new -d -s bakery COMMAND makes it without attaching, with the command in its first pane. tmux split-window takes a command too, and tmux has-session -t bakery succeeds only when the session exists (tmux: Panes, Windows and Sessions).
  • A default: ${2:-30} is the second argument, or 30 when there isn't one.
  • Waiting with a limit: until curl -s -o /dev/null "$url"; do sleep 1; done waits for an answer, and timeout stops it at the limit with exit code 124. timeout runs one command, so the loop goes inside bash -c '...' (Waiting for Things: watch, until and timeout).
  • Only the photos, spaces and all: find -name '*.jpg' -print0 and xargs -0 keep each name whole, -n 1 gives each resize.sh one name, and -P 4 runs four at once (Running Work in Parallel: &, wait and xargs -P).
  • Counting what's done: small=(small/*) puts every file in small/ into an array (Arrays), and ${#small[@]} is how many there are.

When you're done

Every check passes:

check.bats
 ✓ the scripts, the site and the photos are here
 ✓ 1) the key that splits a pane side by side
 ✓ 2) the key that detaches
 ✓ 3) the command that brings back the session bakery
 ✓ 4) the command that reruns the tests every 5 seconds
 ✓ 5) the exit code timeout gives when it stops a command
 ✓ 6) wait-for-site.sh with no URL shows how to use it
 ✓ 7) wait-for-site.sh waits for a server that is starting up
 ✓ 8) wait-for-site.sh gives up after SECONDS
 ✓ 9) wait-for-site.sh waits 30 seconds when SECONDS isn't given
 ✓ 10) resize-all.sh resizes every photo and nothing else
 ✓ 11) resize-all.sh runs four at a time
 ✓ 12) resize-all.sh waits for every photo, then counts them
 ✓ 13) three-panes.sh makes the session bakery with three panes
 ✓ 14) the server runs in one of the panes
 ✓ 15) the tests rerun every 5 seconds in another pane
 ✓ 16) three-panes.sh run a second time makes nothing new

17 tests, 0 failures

Now put two of your tools together: which line, added to three-panes.sh after the session is made, would wait until the site answers before attaching, and give up after 30 seconds?

Looking back

This is the last exercise of Bash in Depth. Think back to the first chapter: you typed pwd in a practice folder called trip, and the terminal answered with a path you had to learn to read. Since then you've moved, copied and deleted files without a mouse, searched a month of logs in a second, set up your shell the way you like it, and written scripts that take arguments, make decisions, loop, fail well and report back: a recipe box app, a weather report from a real web service, a backup that runs while you sleep, a deploy over SSH. In this chapter you ran a server, its tests and your own typing side by side, and made a computer do four jobs at once.

That doesn't mean you know every command, and nobody does. It means the terminal is a place you can work, a manual page or --help is something you can read, and a script file is an application you can write whenever a job calls for one. The mouse is still there, and it's still the better tool for plenty of jobs; now you have a choice. A good next step is a job you do by hand every week: write the script for it.

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-three-things-at-once), 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:

    git init -b main
    git add .
    git commit -m "My solution"
    git remote add origin https://github.com/your-name/exercise-bash-three-things-at-once.git
    git push -u origin main
    

    If 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-three-things-at-once.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-three-things-at-once, 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

A server, its tests and your typing side by side, a wait that knows when to give up, and work spread over every core: that's a developer's terminal, and you built it from scripts you wrote yourself.

Key terms

Related