Linux+ Break Room · Case 08
The script that whispers lies
"SUCCESS: backed up 6 files." There are only 4 files. It never backed up most of them.
Every night, a little bash script backs up the house's most precious papers. Every night it whispers SUCCESS. Nobody has checked in months. Agatha's diary has never been saved. A strange file called 100 keeps appearing next to it. And when a file can't be read at all, it still says everything went perfectly. Catch every lie, and make it tell the truth.
🧰 What you need
- Any Linux shell: an old laptop, a Pi, a VM, or WSL on Windows.
- No sudo. Everything happens in
~/breakroom/mission-08. - Run
--checkas yourself, not with sudo (root can read locked files, which spoils the test). - Bonus tool, if you like:
shellcheck(sudo apt install -y shellcheck) finds most of these lies by itself. Try without it first.
Get the case
Rule 4: read it before you run it, less breakroom-mission-08.sh. The top of the file lists everything it creates and how --clean removes it.
curl -O https://arunnetworkingpro.com/labs/breakroom-mission-08.sh
bash breakroom-mission-08.sh
cd ~/breakroom/mission-08
At any point, ask the house how many ghosts are left:
bash breakroom-mission-08.sh --check
Part A: catch it lying
Does it keep its promise?
./backup.sh
ls data | wc -l
ls vaultHow many files does it say it saved? How many are really in the vault? Which ones are missing?
Hint
Compare the number it prints with ls data and ls vault. What do the missing files have in common? Look at their names.
Watch it think
bash -x ./backup.shFollow what really happens to Agatha's diary.txt. Then fix the loop so every file makes it, whatever its name.
Hint
bash -x prints every command after expansion. $(ls ...) splits names at spaces, and $f without quotes splits them again. Loop over a glob instead: for f in "$SRC"/*, and quote every variable: "$f". And stop hiding errors with 2>/dev/null.
The file called 100
Where does the file called 100 come from? Fix the test so it compares numbers.
Hint
Inside [ ], > isn't "greater than", it's a redirect into a file. Numbers use -gt: [ "$count" -gt 100 ]. Or bash's own (( count > 100 )).
Part B: make it honest
SUCCESS, no matter what
chmod 000 data/candles.list
./backup.sh; echo "exit code: $?"A file can't be read, and it still says SUCCESS with exit code 0. Make it report the failure and exit with a non-zero code. Then chmod 644 data/candles.list again.
Hint
Three lies stack up here. | true throws away tar's exit status. [ $? ] is always true (it only tests "is this text non-empty?"). And exit 0 at the end overrides everything. Count failed copies, check tar directly with if ! tar ...; then, and exit 1 when anything failed.
Count the truth
Make it count files it actually copied, not words it looped over.
Hint
Only add one when cp succeeds: if cp -- "$f" "$DEST/"; then count=$((count+1)); else failed=$((failed+1)); fi.
✅ How you know you won
bash breakroom-mission-08.sh --check tests your script on fresh copies, a normal night and a night with a locked file, and says Verdict: 4/4. No more whispers. The script tells the truth now. Case closed.
Answer key (no peeking until you've tried)
A1: it says 6 files; only 2 reach the vault (candles.list, notes.txt). Both names with spaces are lost. A2: $(ls) plus missing quotes split Agatha's diary.txt into Agatha's and diary.txt; 2>/dev/null hid the errors. A3: [ $count > 100 ] redirects into a file named 100; use -gt. B1: remove | true, [ $? ] and the unconditional exit 0; exit 1 if tar or any copy failed. B2: count inside if cp ...; then.
💥 Let it haunt you again
Run the script again. The haunting starts over, fresh:
bash breakroom-mission-08.shNow do it without the hints. And when you're done for good: bash breakroom-mission-08.sh --clean.
🧠 The ghost, explained
Verdict: not a ghost. Seven ordinary bash mistakes, each one hiding the one before it.
Quote your variables. Always. Unquoted $f is split at spaces and has wildcards expanded. "$f" stays one word. Filenames with spaces aren't rare: they're what real people make.
Never loop over ls. for f in $(ls) turns a list of names into a pile of words. for f in "$dir"/* hands you each name whole.
Exit codes are how scripts tell the truth. 0 means success, anything else means failure, and every command sets $?. Anything after a command (even echo) replaces it, | true hides it, and exit 0 at the end overrules everything. Cron, systemd and other scripts all trust your exit code, so a script that always exits 0 is lying to all of them.
[ ] is a command, not magic. [ takes words as arguments, so > means redirect, -gt means greater-than, and [ $? ] just checks "is there some text?". set -euo pipefail and shellcheck catch a lot of this for you.
← Case 07 · All cases · Case 09 → · Stuck, or found a better way? Email me
🎉 Got it, thank you!
Your comment just landed in my inbox. I read every one, and I'll reply by email.
🤔 That didn't go through
Something in the form looked off. Check your name, email and comment and try again, or just email me.
🐢 Whoa, slow down
That's a lot of comments in a short time, so the box is taking a breather. Try again later, or email me.
😴 The comment box is napping
My server is taking a quick break, so your comment couldn't be sent. Sorry! Please email me instead.
💬 Leave a comment
Stuck, found a better way, or just built it and want to brag? Tell me. It comes straight to my inbox (nothing is posted publicly), and I'll reply by email.
Your email is only used to reply to you. Never shared, never added to any list.