LESSON 2 OF 4
Define a report that cannot quietly lie
Write the contract before implementation
The command accepts exactly two arguments: an existing input directory and a new output report whose parent already exists. The output parent must be outside the input directory tree. The report has three tab-separated columns: a Bash-quoted display filename, a byte count and a SHA-256 content hash. Every selected path must be a readable, non-symlink regular file. Any failed read, write or publication stops the invocation with status 2; success returns status 0 and a scoped summary.
This contract deliberately rejects matched symlinks rather than following them. A symlink can point outside the declared source or change its target. A directory called archive.txt also matches the spelling rule but is not a regular file, so it causes a failure rather than a misleading row. Exclusion and rejection differ: a hidden name is outside scope, while a matched unacceptable path violates the selected-population contract.
Choose that behavior consciously. An alternative collector could report rejected paths alongside valid rows, but it would need a processing status, counts and evidence of each rejection. Dropping the path silently and calling the remaining population complete is not equivalent to either design.
Count bytes, not characters or lines
wc -c counts bytes read from standard input. A text file containing abc without a newline has three bytes. hello followed by a newline has six bytes. A character such as é can occupy more than one byte under UTF-8, so a byte count is not a character count. wc -l counts newline characters; it does not answer the size question.
Reading the file through standard input avoids having wc append a filename to the result. The script checks that the result is numeric before publishing it. A hash is also computed from standard input, so awkward filename printing does not interfere with extraction of the digest. A hash identifies the bytes read for that calculation; it does not prove who created them, that they were safe, or that the earlier size read saw the same version.
That last qualification is important. The file is read twice: once for size and once for hash. A concurrent writer can modify it between reads. The source directory is not locked or snapshotted. For a real evidentiary collection, use an approved stable export or redesign the read path around one controlled byte stream. A local exercise that succeeds is not a production consistency guarantee.
Separate diagnostic output from report output
The report is written through file descriptor 3. Progress goes to standard output and failures go to standard error. This keeps a sentence such as Published 2 file rows out of the TSV. Mixing diagnostic text with data can break downstream parsing while looking fine in a terminal.
Notice that status is checked directly at important operations: a failed command invokes fail. set -u catches expansion of unset variables. We do not claim that adding set -e automatically creates a correct error policy. Its behavior depends on command context; explicit decisions remain necessary for reads, writes and publication. Familiarize yourself with each operation's own success contract.
Publish only after the report is complete
Writing directly to the final filename exposes a partial report if the fifth file fails. Instead, create an unpredictable temporary file in the output parent, write all rows, close it, and create the final name with a hard link. GNU ln -T refuses an existing destination, including a directory or symlink; it does not treat that directory as a place to deposit a file. This final operation is the no-overwrite boundary, not just the earlier existence check.
The temporary and final names refer to the same completed file after linking. Cleanup removes only the temporary name. The report remains. mktemp ordinarily creates a mode-600 file; the final name shares those permissions. This does not guarantee crash durability, signed integrity, long-term retention or support for every filesystem. A failure to make a hard link is a reported publication failure, not permission to overwrite by copying instead.
The earlier check improves the message, but another process could create the destination afterward. The exclusive link still refuses it. Tests should cover the final primitive and not merely congratulate the preflight check. The source tree should also be controlled during collection: the regular-file and symlink checks are observations, not a race-proof sandbox against malicious directory changes.
Exercise: design the failure matrix
Before reading the script, fill in expected output existence and exit status for: no matches; an existing report; a dangling output symlink; a symlink input match; an output parent inside input; a missing input directory; a directory named bad.txt; and a file that changes while being read.
Worked expectations
No matches succeeds with a header-only report. Existing destinations and unacceptable selected paths fail without a new published report. Missing input and an inside-source output fail before collection. A changing file may produce internally inconsistent observations without an error; that is a documented limitation requiring source-stability controls, not a test result to hide. Normal-user unreadable-file behavior needs a test under that identity; root’s ability to read is not proof of that behavior.
Evidence laboratory — attempt before reading the solution
In a disposable fixture directory, files are named daily report.txt, notes*.txt and -summary.txt. Task: copy each selected text file into an already-approved output directory and record success/failure without deleting originals. A script uses for f in $(ls *.txt); do cp $f $dest; done. Explain at least three ways the argument handling can break, then propose a safer iteration and copy design for the chosen Bash environment.
YOUR NEXT STEP
Keep building your skills
No directly related CogniSkill book is mapped to this course yet. Continue with the free learning, or compare the publishing catalogue by your next goal.
Explore books by topic and goal →Sign in to save lesson completion. Reading position is separate from completion.
Practice notes — your working space
Save predictions, outputs and explanations as you work. Notes stay on this browser, not your account; people sharing this browser can access them. Avoid passwords and sensitive data. Export to keep a portable copy, or clear your notes before leaving a shared device.
How was this lesson?
Help us improve this lesson.