Building From Scratch
This is it: build the log archiver specified in the previous chapter, entirely on your own. No version-by-version walkthrough this time, no partial script handed to you at the start of the chapter — just the specification, your own judgment, and everything this course has covered across the previous twenty-seven chapters. This chapter won’t hand you a finished script. That’s the point.
Your Task
Build log-archiver.sh against the full specification from the previous chapter: argument parsing, config file support with correct precedence, path validation, locking, safe null-delimited file iteration, staged compression with proper error handling per file, retention-based deletion, a summary report, structured logging, --dry-run support, and trap-based cleanup. Use the test workspace you prepared at the end of that chapter to validate your own work as you go, rather than writing the whole thing before running any of it once.
Work in the order the implementation plan suggested — CLI and validation first, then logging, then locking, then the archiving step (dry-run only, at first), then the deletion step (dry-run only, at first, given the stakes), then the summary, with cleanup wired in from the start rather than added at the end. Nothing about this task requires syntax you haven’t already used successfully in this course — find -print0 combined with read -r -d '', command arrays for building gzip and find invocations safely, the lockfile pattern, the config-then-CLI precedence order, the staging-then-atomic-commit idea applied to a dated archive directory instead of a single destination. Every piece has already appeared somewhere in this course; the work here is assembling them into something new yourself.
A Self-Check Checklist
Once you have something you believe works, verify it against every scenario below — not just the happy path. A script that only handles the case where everything goes right isn’t finished, by this course’s own standard, all the way back from Chapter 1.
- Running against your prepared test workspace archives
old-app.log,ancient.log, andservice report.log(all older than the default 7-day archive threshold), and leavesapp.loguntouched. - The archived
service report.log— the one with a space in its name — survives the entire process with its filename intact, not split or mangled. - Running the script a second time immediately afterward does something sensible — it shouldn’t try to re-archive files that are already archived, or error out confusingly.
- Running with
--dry-runreports exactly what would happen, and afterward, nothing on disk has actually changed. - Pointing
-lat a directory that doesn’t exist produces a clear, specific error and a distinct exit code — no partial action taken. - Passing an empty string or
/for either-lor-ais refused immediately, before anything else happens. - Starting the script, then starting a second instance while the first is still running, results in the second instance logging that it’s already running and exiting cleanly — not two instances racing each other.
- Interrupting the script partway through (
Ctrl+C, orkillfrom another terminal) leaves no half-compressed file and no stray lockfile behind. - Making one file in the log directory unreadable (
chmod 000) causes that one file’s failure to be logged clearly, while every other eligible file still gets processed — and the script’s final exit status reflects that something failed, even though most of the run succeeded. -
-h/--helpand-v/--versionboth work and exit0without doing anything else. - A config file, pointed to via the environment variable your script defines, correctly sets defaults — and a command-line flag correctly overrides whatever that config file set.
If any of these surprise you, that’s the exercise working as intended — go back to the specific earlier chapter each one traces back to (they’re not hard to place, if you’ve been through this course in order) and reconcile the gap.
Best Practices For Tackling This
- Test each stage in isolation before moving to the next one. A locking bug is far easier to find in a five-line locking block you just wrote than buried inside a 150-line script you’re testing for the first time all at once.
- Write the dry-run path first, for both archiving and deletion, and don’t remove training wheels until you’ve watched it report exactly what you expect against your test workspace.
- Deliberately try to break your own script with each scenario in the checklist above, rather than only running it the way you expect it to be run — this is precisely the mindset every safety section in this course has been modeling for you, chapter after chapter.
Shell-Safety Considerations — What Your Script Must Survive
Rather than a naive-then-corrected example this time, here’s the standard your own implementation is held to, stated directly: your script must survive every scenario in the self-check checklist above without ever performing an unintended deletion. That is the one non-negotiable bar for this capstone specifically, given that this is the first script in this course with the power to permanently destroy data as part of its normal operation.
A bug that produces a wrong log message is a bug. A bug that deletes the wrong file, because a path variable was empty, or a glob matched something unexpected, or a race condition let two instances both reach the deletion step at once, is the kind of failure this entire course has been built around preventing — and it’s the one you’re now responsible for guarding against yourself, without a chapter walking you through the fix.