Exercise: The Health Check

Finish a health check script for a book club's home server: read the disk, memory, load and failed services, warn about each problem, and exit with a code that says whether all is well, while a check script runs it on a pretend computer, job by job.

Journeyman · Unix · Linux · Updated

The book club's website runs on a small home server in Sam's closet, and Sam doesn't want to sign in every morning to read four gauges. The club wants one command that answers "does the server need a look?": a line each for the disk, the memory, the load and the services, a plain warning for each problem, and an exit code that says whether all is well, so another script can act on it. You'll finish health.sh, and a check script runs it on a pretend computer with a full disk, too little memory or a failed service, job by job.

Before you start

You need a terminal: Ubuntu on Linux or in WSL on Windows, or the Terminal app on a Mac. The check runs with bats, from Testing Scripts with bats. 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. health.sh reads free and systemctl, which Linux and WSL have and a Mac doesn't; on a Mac, the check still works, because it brings its own pretend versions.

Get the exercise code

Open this lesson's GitHub repository, github.com/ask-codey/exercise-bash-the-health-check, 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:

  • health.sh: the health check to finish, with five jobs, each a comment that says what to read, what to print and when to warn.
  • check.bats: the check. You don't need to change it.

What to do

cd into the exercise folder, run chmod +x health.sh, and open it with nano health.sh. Strict mode, the warnings list and the first line are there already. Each job is a comment, with room under it for your lines:

# 1. Disk. Read how full the main disk is, as a number without the %:
#    the fifth column of the second line of  df /  (tr -d % takes the % off).
#    Print:  Disk:    42% of / used
#    When it's 90 or more, add the warning:
#      the disk is 95% full. Find what's filling it with du.
  1. Disk: the Use% of / from df /, a Disk: line, and a warning at 90% or more.
  2. Memory: the total and available MB from free -m, a Memory: line, and a warning when less than 10% is available.
  3. Load: the 1-minute load from uptime and the CPUs from nproc, a Load: line, and a warning when the load's whole number is at least the number of CPUs.
  4. Failed services: their names from systemctl --failed --no-legend --plain, a Failed services: line with how many, and a warning for each.
  5. The verdict: every warning, each on a line that starts WARNING: , then All OK. and exit code 0, or Warnings: and the number, and exit code 1.

Each job's comment gives its line and its warning word for word. Save, and run ./health.sh as you go.

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 names its job, says what it expected and shows what your script printed, for example Job 1: with the disk 42% full, health.sh should print the line Disk: 42% of / used. The check makes small stand-ins for df, free, uptime, nproc and systemctl in a throwaway folder of its own, so it can pretend the disk is full without touching yours. Its last check runs health.sh on your own computer.

Hints

  • Try each reading at the prompt first. df / | awk 'NR == 2 {print $5}' should print your disk's percentage; then add | tr -d % and save it with $( ) (Values from Commands: $( ) and $(( ))).
  • (( )) compares whole numbers, so free -m (MB) works and free -h (3.8Gi) doesn't. For the 10%, multiply first: available * 100 / total.
  • The load has a dot in it, and (( )) can't count with that. ${load%.*} cuts off the dot and what follows, the way ${f%.tmp} cut off an ending in For Every File: for Loops.
  • Each failed service's name is the first word of its line. Loop over them with for, counting as you go (Stopping Early: break, continue and Counters).
  • Job 5 is two parts: a for loop over "${warnings[@]}", then ${#warnings[@]} to choose All OK. or Warnings: (Arrays).

When you're done

Every check passes:

check.bats
 ✓ the script is here and bash can read it
 ✓ 1) the disk line
 ✓ 1) a disk 90% full or more gets a warning
 ✓ 2) the memory line
 ✓ 2) less than 10% of memory available gets a warning
 ✓ 3) the load line
 ✓ 3) a load of one per CPU or more gets a warning
 ✓ 4) failed services are counted
 ✓ 4) each failed service gets a warning
 ✓ 5) a healthy computer: All OK. and exit code 0
 ✓ 5) two problems: two warnings, Warnings: 2 and exit code 1
 ✓ it runs on this computer too

12 tests, 0 failures

And on Sam's laptop, ./health.sh then echo $? prints this; yours shows your own computer's numbers:

Health check, 2026-11-09 14:42
Disk:    4% of / used
Memory:  62043 MB available of 64194 MB
Load:    0.42 on 24 CPUs
Failed services: 0
All OK.
0

Want to see a warning on your own computer? Change job 1's 90 to 1 for a moment, run it, and change it back. Then think about the exit code: what could a script that runs health.sh every morning do with a 1 that it couldn't do with the words alone?

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-the-health-check), 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-the-health-check.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-the-health-check.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-the-health-check, 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 check an administrator runs first on any server, written so both people and other scripts can read its answer: plain lines for you, and an exit code for the script that runs it on a schedule.

Key terms

Related