CogniSkill · Bash Scripting for Everyday Automation
Content review copy. This does not replace the site theme. For SC-300, this preview shows additions; the existing teaching is retained on import.
Build a read-only file inventory that handles awkward filenames, records byte counts and hashes, and refuses to overwrite an existing report. Then break it deliberately and show which independent tests catch the defect. This is an automation course, not a collection of terminal tricks: the deliverable includes a defined population, explicit failure behavior and evidence a colleague can review.
Use Bash 4 or later on Linux with GNU coreutils. The validation environment uses Bash 5.2. The commands are not a promise of compatibility with macOS utilities, PowerShell or a POSIX-only shell. Linux Foundations is useful preparation; you should understand directories, a command's exit status and standard output versus diagnostic output. Work in a new local directory with synthetic files. No administrator account, production data or network access is required.
The project inventories visible top-level files ending in .txt. It does not recursively search, include hidden files, measure filesystem allocation or establish that files remained unchanged during collection. You will learn why those limits must appear beside the result instead of being buried in an installation note.
Four chapters, with practical work
- Predict what the shell passes to a command
- Define a report that cannot quietly lie
- Build, trace and inspect the complete inventory
- Prove the collector's limits and extend it deliberately
Applied capability and learning route
Process filenames and command failures safely in a bounded local script.
Follow the lessons in order: establish the model, trace the supplied evidence, attempt the case, compare the worked solution, and complete the changed-condition project. The target is intermediate reasoning within this stated scope, not mastery of the entire profession or product. If a prerequisite is unfamiliar, pause and use the linked foundational teaching before attempting the project.
All teaching, explained answers and core synthetic practice are free. Some operational labs require separately available software, a licensed test tenant or an authorized lab; offline completion does not claim hands-on product proficiency.
How to complete this course
Study the worked examples, attempt the exercises before opening solutions, and submit your own project notes. Completion records are self-reported; practical competence requires the stated verification.
Predict what the shell passes to a command
Start with an argument, not with a command name
A shell command receives an argument list. Before that command runs, Bash processes the words you typed. Variable substitution, word splitting and filename expansion can change that list. Many automation mistakes happen before the intended program sees anything.
Consider a directory called input with spaces. The variable contains a single path string, but an unquoted expansion can split it into three arguments. A command expecting one directory now receives something else. Quoting is part of the interface contract: "$input" asks Bash to pass the expanded value as one argument. It is not cosmetic punctuation.
Run this small inspection in your practice terminal:
input='input with spaces'
printf '<%s>\n' $input
printf '<%s>\n' "$input"
The first command prints three lines: <input>, <with> and <spaces>. The second prints one: <input with spaces>. printf reuses the format for additional arguments; this makes the splitting visible without needing a real collector. Predict the output before running it. If your shell has unusual global settings, record them rather than assuming the course's default context.
Single quotes prevent expansion of the enclosed text; double quotes preserve an argument while allowing selected expansions such as $input. To print the literal string $input, use printf '%s\n' '$input'. To print its current value, use printf '%s\n' "$input". The format is constant and the data is a separate argument. Do not let untrusted text become a printf format or a command to evaluate.
A filename is data, even when it looks strange
Spaces are only the first case. Names can contain tabs, newlines, brackets or leading dashes. A filename resembling an option can affect a command if you do not respect its argument boundary. Many GNU utilities recognize -- as the end of options; rm -- "$path" treats the following argument as a path rather than an option. That is useful syntax, but it does not authorize deletion or make a broad target safe.
The inventory will not delete input. Its only cleanup target is the exact temporary report created by mktemp. This matters more than adding defensive punctuation to a dangerous deletion command. Determine side effects first, then implement the argument contract.
Avoid for file in $(ls ...). That approach turns display text back into shell words. It loses filename boundaries and can quietly change the population. A better small-directory interface is a Bash array expanded as "${files[@]}": each element remains one argument even if it contains whitespace.
names=('alpha.txt' 'two words.txt' '-n.txt')
printf 'items=%s\n' "${#names[@]}"
printf '<%s>\n' "${names[@]}"
Expect items=3 followed by one line per name. Contrast this with "${names[*]}", which joins the elements into one argument using a separator. Neither expansion is universally better; they answer different interface questions. The loop needs the separate-element form.
Matching is a population decision
The expression "$input"/*.txt keeps the directory portion literal but lets *.txt expand. Quoting the entire expression as "$input/*.txt" would suppress that expansion and pass a literal asterisk. Leaving the directory variable unquoted reintroduces splitting.
By default, an unmatched glob can remain as a literal word. An empty input directory might therefore produce a loop iteration over /path/*.txt instead of zero iterations. Our script enables nullglob: no matches produce no array elements. Empty is a valid observation under this small lab contract, not an error and not a fabricated filename.
Do not generalize empty to successful collection in every system. A real export might be empty because access failed or the wrong directory was selected. Our report states the directory scope and only claims what this invocation observed. The later handoff exercise requires you to ask whether that directory is an authoritative source for the business question.
Exercise: predict five argument lists
For each case, say how many arguments follow the format and whether matching happens: unquoted $input; "$input"; "$input"/*.txt; "$input/*.txt"; "${names[@]}". Test with two visible .txt files, one hidden .txt, one nested directory and one file with another extension.
Worked reasoning
With the supplied space-containing value, unquoted expansion produces three words in the default context. The quoted value produces one. The partially quoted glob produces one element per visible top-level match, while the fully quoted pattern produces one literal pattern. The quoted array expansion preserves three elements. Hidden and nested files are outside the glob's population; that is an explicit scope, not evidence that those files do not exist.
Reasoning model
A shell expands unquoted variables through word splitting and pathname expansion, so a filename containing spaces or wildcard characters can change argument structure. Preserve argument boundaries with quoting and appropriate arrays or null-delimited traversal. A script’s exit behavior needs deliberate design: pipeline status and error-handling options have context-dependent rules. Test failure paths instead of assuming a single shell option makes every operation safe.
Official reference for further study
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.
Official reference for further study
Build, trace and inspect the complete inventory
Prepare an isolated fixture
Create a new directory and use sibling input and output directories. Do not run the setup inside a directory containing real files with the same names.
mkdir -p -- practice/input practice/output
printf 'abc' > practice/input/alpha.txt
printf 'hello\n' > 'practice/input/two words.txt'
printf 'outside scope\n' > practice/input/notes.log
printf 'hidden\n' > practice/input/.hidden.txt
These setup commands intentionally create or overwrite the named fixture files. They are safe only in your new practice directory; they are not a source-preservation promise. The inventory itself has a different, read-only source contract. Separating setup from collection keeps that distinction clear.
Save the supplied script as inventory.sh. Run it with Bash rather than assuming the default shell is compatible.
bash inventory.sh practice/input practice/output/report.tsv
Expect the message Published 2 file rows; scope: visible top-level *.txt only. The report contains a header and two rows. Their byte counts are 3 and 6. The display name for the second row is two\ words.txt. It is escaped for display; never feed the report to eval to reconstruct filenames. Reversible transport of arbitrary pathnames belongs in a designed format and trusted parser, not an invitation to execute a field.
Complete program
#!/usr/bin/env bash
# Read visible top-level *.txt files; publish one new TSV without overwriting.
set -u
export LC_ALL=C
fail() { printf 'Inventory failed: %s\n' "$1" >&2; exit 2; }
[[ $# == 2 ]] || fail 'usage: bash inventory.sh INPUT_DIRECTORY NEW_REPORT.tsv'
for tool in wc sha256sum mktemp ln rm dirname basename; do
command -v "$tool" >/dev/null || fail "required command unavailable: $tool"
done
input=$(cd -- "$1" && pwd -P) || fail 'input directory unavailable'
parent=$(cd -- "$(dirname -- "$2")" && pwd -P) || fail 'output parent unavailable'
output="$parent/$(basename -- "$2")"
[[ $parent != "$input" && $parent != "$input/"* ]] || fail 'output must be outside input tree'
[[ ! -e $output && ! -L $output ]] || fail 'output already exists'
temp=$(mktemp -- "$parent/.ck-inventory.XXXXXXXX") || fail 'cannot create temporary file'
cleanup() { [[ -z ${temp:-} ]] || rm -f -- "$temp"; }
trap cleanup EXIT
trap 'exit 130' INT
trap 'exit 143' TERM
exec 3>"$temp" || fail 'cannot open temporary report'
printf 'filename_bash_quoted\tbytes\tsha256\n' >&3 || fail 'cannot write header'
shopt -s nullglob
files=("$input"/*.txt)
count=0
for path in "${files[@]}"; do
[[ ! -L $path && -f $path && -r $path ]] || fail 'matched path is not a readable non-symlink regular file'
size=$(wc -c < "$path") || fail 'byte count failed'
size=${size//[[:space:]]/}
[[ $size =~ ^[0-9]+$ ]] || fail 'unexpected byte count'
digest=$(sha256sum < "$path") || fail 'hash read failed'
hash=${digest%% *}
[[ $hash =~ ^[0-9a-f]{64}$ ]] || fail 'unexpected hash'
name=${path##*/}
printf '%q\t%s\t%s\n' "$name" "$size" "$hash" >&3 || fail 'cannot write row'
count=$((count + 1))
done
exec 3>&- || fail 'cannot close report'
# GNU ln -T refuses an existing target, including directories and symlinks.
ln -T -- "$temp" "$output" || fail 'exclusive report publication failed'
printf 'Published %s file rows; scope: visible top-level *.txt only\n' "$count"
Trace the boundaries in order
At startup, verify the argument count and required tools. Resolve the input directory and output parent physically so a symlinked parent does not disguise an output inside the source tree. Preserve the final destination name: the link operation must refuse an already existing target rather than follow it.
After creating the temporary file, install cleanup handlers. An ordinary error or exit removes only that known path; interrupt and termination handlers return explicit statuses. A forced kill or machine crash cannot execute cleanup, so a temporary file can remain. It is a leftover to inspect, not an excuse for recursively deleting broad directories.
Build the array once using the declared glob. Walk its elements, reject unacceptable matches, read bytes and hash, then write one row. Finally close the descriptor and publish. Use bash -n inventory.sh to check syntax before running. Syntax success does not exercise reads, permission decisions, empty matches or final publication; it is one layer of evidence.
Fault lab with visible evidence
Run the same output path twice. The second run must fail and leave the report bytes unchanged. Create a dangling symlink with a .txt name inside a separate input fixture: no report should be published. Create a directory ending in .txt: it should also fail. Try an output under input and confirm that no temporary report is left there.
Next create -n.txt, a tab-containing name and a newline-containing name. Use the supplied tests if your terminal makes those names difficult to create. The TSV should still have exactly one physical row per selected file and exactly three columns. %q makes control characters visible without putting an actual tab or newline into the filename field. This is a display contract specific to Bash, not generic CSV quoting.
List the chosen directory before and after collection and compare input bytes. Record the runtime, invocation, source scope, report, status and diagnostics. If a test fails, keep its actual result and isolate one hypothesis. Re-running until a green message appears without explaining the repair loses the debugging evidence the project is meant to produce.
Worked laboratory solution
Compare your reasoning with the worked solution
Command substitution and unquoted expansion split daily report.txt into separate words. The literal wildcard in notes*.txt can be expanded again when unquoted. A leading hyphen can be interpreted as an option by utilities unless protected using supported -- syntax or an appropriate path prefix. Parsing ls is not a reliable filename interface. In a flat directory, a carefully quoted glob loop with explicit no-match handling can suffice; recursive traversal may need null-delimited find/read processing. Check each copy’s status and preserve a clear summary rather than printing unconditional success.
Official reference for further study
Workbook already downloaded
Prove the collector's limits and extend it deliberately
Independent tests with literal expectations
The download includes the script and this Python standard-library test harness. Python is used only to construct awkward filenames and assert external behavior; the collector remains Bash. If you prefer another harness, keep independent fixture expectations and subprocess exit-status checks.
from pathlib import Path
import hashlib
import subprocess
import tempfile
import unittest
SCRIPT = Path(__file__).with_name('inventory.sh')
class InventoryTests(unittest.TestCase):
def setUp(self):
self.tmp = tempfile.TemporaryDirectory()
self.root = Path(self.tmp.name)
self.source = self.root/'input with spaces'
self.output = self.root/'out'
self.source.mkdir(); self.output.mkdir()
def tearDown(self):
self.tmp.cleanup()
def run_report(self, output=None):
return subprocess.run(['bash',str(SCRIPT),str(self.source),str(output or self.output/'report.tsv')],capture_output=True,text=True)
def test_literal_sizes_hashes_and_quoted_name(self):
(self.source/'alpha.txt').write_bytes(b'abc')
(self.source/'two words.txt').write_bytes(b'hello\n')
self.assertEqual(self.run_report().returncode,0)
lines=(self.output/'report.tsv').read_text().splitlines()
self.assertEqual(lines[0],'filename_bash_quoted\tbytes\tsha256')
self.assertEqual(lines[1], 'alpha.txt\t3\t'+hashlib.sha256(b'abc').hexdigest())
self.assertEqual(lines[2], 'two\\ words.txt\t6\t'+hashlib.sha256(b'hello\n').hexdigest())
self.assertEqual((self.source/'alpha.txt').read_bytes(),b'abc')
def test_empty_population_not_literal_glob(self):
self.assertEqual(self.run_report().returncode,0)
self.assertEqual(len((self.output/'report.tsv').read_text().splitlines()),1)
def test_existing_report_preserved(self):
report=self.output/'report.tsv';report.write_text('KEEP\n')
self.assertEqual(self.run_report().returncode,2)
self.assertEqual(report.read_text(),'KEEP\n')
def test_inside_input_refused(self):
self.assertEqual(self.run_report(self.source/'report.tsv').returncode,2)
self.assertFalse((self.source/'report.tsv').exists())
def test_symlink_match_refused_without_report(self):
(self.source/'alias.txt').symlink_to('/does-not-exist')
self.assertEqual(self.run_report().returncode,2)
self.assertFalse((self.output/'report.tsv').exists())
self.assertEqual(list(self.output.iterdir()),[])
def test_hidden_and_nested_outside_scope(self):
(self.source/'.hidden.txt').write_text('secret')
nested=self.source/'nested';nested.mkdir();(nested/'child.txt').write_text('nested')
self.assertEqual(self.run_report().returncode,0)
self.assertEqual(len((self.output/'report.tsv').read_text().splitlines()),1)
def test_tab_newline_and_leading_dash_names(self):
for name in ['-n.txt','tab\tname.txt','line\nname.txt']:
(self.source/name).write_bytes(b'x')
self.assertEqual(self.run_report().returncode,0)
lines=(self.output/'report.tsv').read_text().splitlines()
self.assertEqual(len(lines),4)
self.assertTrue(all(len(line.split('\t'))==3 for line in lines))
self.assertTrue(all(line.split('\t')[1]=='1' for line in lines[1:]))
if __name__ == '__main__':
unittest.main()
Run python -m unittest -v from the downloaded project directory. Seven tests cover the baseline, empty input, no-overwrite behavior, output separation, rejected symlink matches, excluded hidden/nested files, and difficult names. The baseline hashes are calculated from literal fixture bytes, not from the report under test. That independence matters: a test which simply calls the same faulty hash-extraction helper can repeat its mistake.
Make a defect and explain the failing assertion
In a separate copy, remove shopt -s nullglob. The empty-population test should now fail because the literal pattern reaches validation. Restore it. Next change "${files[@]}" to unquoted ${files[@]}. The space-containing filename test should fail. Restore it before experimenting further.
Do not treat mutation as a competition to invent dangerous code. It is a controlled exercise in your fixture directory. The useful result is an explanation: which requirement was violated, which test observed it, and whether other requirements remain covered. Passing these mutations does not prove the script secure against malicious filesystem races or suitable for unattended production work.
Project A — Change scope without hiding the change
Design an optional --include-hidden mode. Write expected results before implementing it: the baseline should gain exactly one row, .hidden.txt, but nested files should remain excluded. Be explicit about whether matching dot names requires dotglob, whether those names contain sensitive material, and how the run's scope is recorded. A hidden-file flag is a population change, not merely a cosmetic preference.
Do not add recursion by reusing a newline-separated find display. If recursion is a future requirement, design a NUL-delimited path interface and a validation policy for links, mount boundaries, permission failures and huge populations. Keep that proposed design separate from the current collector's release claim.
Project B — Add a threshold without changing the report grain
Add a review flag for files with more than five bytes. The baseline has one review file: two words.txt with six bytes. A file containing exactly five bytes is not selected under a strict-greater-than rule. Test 4, 5 and 6 independently, plus an empty file. The threshold must be a validated integer argument, not arbitrary text inserted into arithmetic evaluation.
Retain all source rows, adding a classifier field rather than dropping small files. Distinguish the number of inventory rows from the number of review rows. If an analyst asks for a total size, state that it is a sum of observed byte counts in this population, not disk allocation, compression size or backup-storage consumption.
Project C — Write a handoff another operator can trust
Deliver a runbook describing prerequisites, scope, input ownership, output permissions, no-overwrite behavior, failed-run evidence and cleanup of abandoned temporary files. Name the assumption that inputs are stable. Explain how you would test unreadable files as a normal user; the supplied validation did not establish that access denial under an unprivileged identity.
For an unattended job, define its working directory, environment, approved identity, scheduling overlap policy, bounded runtime and notification owner. A script that works in your interactive terminal does not inherit those controls. Use a new output name per scheduled run and decide who may read hashes and filenames. Even a read-only collector can expose sensitive metadata.
Acceptance and references
Submit the fixture, original and repaired scripts, seven passing tests, at least two caught defects, a threshold extension and a short evidence note. The note must distinguish byte count from character count, no-match from collection failure, excluded path from rejected path, and completed publication from crash durability.
Consult the GNU Bash Reference Manual sections on quoting, filename expansion, arrays, redirections and traps, and GNU coreutils documentation for wc, mktemp and ln. Those explain the language and utility interfaces; the synthetic case and acceptance requirements here are original course exercises. No paid book is needed to finish this standalone project.
Official references: Bash Reference Manual and GNU coreutils manual.
Transfer project — changed conditions
Implement the fixture with spaces, wildcard characters, leading hyphens and, if supported by your test, a newline in a filename. Verify file contents and count after copying. Inject an unwritable destination and show a failure status without deleting sources. State your Bash/utility assumptions and avoid broad recursive cleanup commands. A safe test harness should use a known disposable directory with an explicit path.
Practical assessment rubric
Score each dimension 0–2: 0 = absent or materially incorrect; 1 = partly correct with an unresolved gap; 2 = correct, explicit and supported by the supplied evidence or an actual labeled lab result.
- Evidence/model: identify the relevant entities, inputs, assumptions and boundaries without inventing facts.
- Main result: derive the case-specific expected result, calculation or decision and show the intermediate reasoning.
- Failure/boundary: test the changed condition and explain why the original conclusion does or does not transfer.
- Verification: supply reproducible steps and expected/observed results; label manual traces as manual, not executed.
- Communication/safety: explain limitations, owners and a proportionate next action without unsafe or unnecessary access.
Target: at least 8/10, with no zero in main result or communication/safety. This is a local learning rubric, not an accredited certification standard. A written/manual solution demonstrates reasoning only; operational competence additionally requires the relevant executable lab in an authorized environment. The native multiple-choice score is separate and does not automatically grade this rubric.
Submission template
- My input/evidence and assumptions:
- Expected result with calculations or trace:
- My implementation/decision and justification:
- Changed-condition result:
- Observed execution, or explicitly labeled manual trace:
- Failure and recovery evidence:
- Unverified limitation and next action:
- Self-score with evidence for each dimension:
Remediation
If the main result is wrong, reconstruct the supplied case from the raw inputs before changing code or controls. If the boundary case is wrong, identify the assumption that changed and rerun both the original and changed case. If verification is missing, do not convert a prediction into an observed result. Reattempt the transfer task without copying the worked solution, then explain it to another person.
Official reference for further study
Assessment: explain your decisions
Choose an answer and write your reason before opening each explanation. Then explain why the other choices do not fit the evidence. Use missed questions to revisit the lesson. This knowledge check is only one part of assessment: complete the practical transfer project as well. It does not predict certification-exam success.
1. Which preserves a name with spaces as one argument?
- "$name"
- An unquoted expansion everywhere
- Deleting spaces
Answer and explanation
"$name" — Quoting preserves the intended argument.
2. Why use a fixed printf format?
- To change file ownership
- To keep input as data rather than format instructions
- To grant root access
Answer and explanation
To keep input as data rather than format instructions — Data should not define the format string.
3. What should a missing input file produce?
- A normal zero-row success
- A silent deletion
- An explicit failure
Answer and explanation
An explicit failure — Missing input is not the same as an empty valid input.
4. The input directory contains spaces. Which expansion passes its path as one argument while allowing a .txt glob?
- "$input"/*.txt
- "$input/*.txt"
- $input/*.txt
Answer and explanation
"$input"/*.txt — Quote the variable expansion to preserve the directory argument, leaving the glob outside those quotes. Fully quoting the pattern makes the asterisk literal; leaving the variable unquoted can split its value.
5. Another process creates the output after the preflight existence check. What protects its contents?
- The earlier check permanently reserves the path.
- The final exclusive publication operation refuses the existing target.
- The collector may overwrite it because the check previously passed.
Answer and explanation
The final exclusive publication operation refuses the existing target. — Preflight checks can race. The final GNU ln -T operation provides the no-overwrite boundary. Publication failure must remain a failure rather than falling back to an overwriting copy.
6. The report has zero rows because only hidden and nested files exist. What does that establish?
- The source machine contains no text files.
- Every file was unreadable.
- No files were selected within the declared visible top-level scope.
Answer and explanation
No files were selected within the declared visible top-level scope. — The population contract excludes hidden and nested files. A header-only report makes a narrow observation, not a machine-wide inventory or a guarantee that the chosen directory is authoritative.
7. Why is unquoted $f unsafe for daily report.txt?
- Word splitting can turn one filename into multiple arguments.
- Spaces are impossible in filenames.
- It automatically encrypts the file.
Answer and explanation
Word splitting can turn one filename into multiple arguments. — The shell’s expansion rules change argument boundaries unless the value is quoted appropriately.
8. What problem can -summary.txt cause?
- It always expands into every file.
- A utility may parse it as an option rather than an operand.
- It cannot exist on a filesystem.
Answer and explanation
A utility may parse it as an option rather than an operand. — Leading-hyphen operands require deliberate option-boundary handling supported by the utility.
9. What demonstrates the copy workflow handled a failure correctly?
- It prints success regardless of command status.
- It removes the source to simplify retry.
- The source remains intact and the run reports the failed copy with a failure status.
Answer and explanation
The source remains intact and the run reports the failed copy with a failure status. — Failure handling should preserve original data and expose incomplete work. A reassuring message is not verification.
Use the explained questions above for offline practice. A scored attempt requires the course assessment on CogniSkill.ca.