Skip to content
← Systems Foundations

Learning bite

Exit codes, linting, and tests

Exercise success and failure paths in small diagnostic scripts.

  • Bash
  • Linux
Documentation reviewed2026-10-26 · 4 min read
  1. Why
  2. Predict
  3. Try
  4. Inspect
  5. Explain
  6. Save evidence

Predict before you try

Can a script print a plausible report while losing the failure of an earlier command?

Try it: An exit status is a result the caller can use →
On this page

An exit status is a result the caller can use

Status 0 conventionally means success; nonzero statuses describe other outcomes according to the command's interface. In inspect-path.sh, we chose 2 for incorrect usage and 1 for a missing path. A successful printf after a failed command can accidentally become the script's final status, so do not infer success from the last visible message.

Use the script saved in the arguments bite, inside the same Linux directory. These commands are intentionally separate so you can inspect each result:

bash
cd ~/foundations-shell
bash -n inspect-path.sh
bash inspect-path.sh .
bash inspect-path.sh
printf 'usage status: %s\n' "$?"
bash inspect-path.sh ./definitely-missing-study-path
printf 'missing status: %s\n' "$?"

Expected usage status is 2 and missing status is 1. $? belongs to the command that just completed; running date or another command before capturing it loses that result. bash -n parses without running the script. It can reject a missing fi, but cannot establish that your path test or chosen status is correct.

Check behavior with an assertion

Save this complete test as check-usage.sh next to the script:

bash
cat > check-usage.sh <<'EOF'
#!/usr/bin/env bash
if bash inspect-path.sh >usage.out 2>usage.err; then
  printf 'FAIL: missing argument was accepted\n' >&2
  exit 1
else
  study_status=$?
fi
if [[ $study_status -ne 2 ]]; then
  printf 'FAIL: expected status 2, got %s\n' "$study_status" >&2
  exit 1
fi
if [[ -s usage.out ]] || ! grep -q '^Usage:' usage.err; then
  printf 'FAIL: usage message went to the wrong stream or is missing\n' >&2
  exit 1
fi
printf 'usage checks passed\n'
EOF
bash check-usage.sh

An assertion compares observed behavior with a stated expectation and fails when they differ. Here -s means a file is nonempty, || means “or” for the command tests, and grep -q checks a match without printing it. The expected success message is usage checks passed. This tests one case; it does not prove every path works.

Change the diagnostic's usage status from 2 to 3 temporarily. The test should now fail and report 3. Restore 2 and rerun. A test that never fails when its required behavior is broken is not a useful check.

Separate syntax, lint, and execution

If installed, run shellcheck inspect-path.sh check-usage.sh. ShellCheck identifies common shell mistakes such as unquoted expansions; it does not run every possible input. shfmt -d can show formatting differences if available. Neither tool must be installed to understand the assertion above.

bash -x inspect-path.sh . runs the script with expanded commands printed as a trace. Use it only with these harmless fixture values: tracing real tokens or passwords can expose them. Start debugging at the first unexpected branch or value, with the actual working directory and interpreter recorded.

set -euo pipefail is not a proof of correctness. -u detects some unset expansions, pipefail changes pipeline status, and -e has exceptions in conditional and command-list contexts. Explicitly handle expected failures as the test does. A syntax check cannot tell you whether a service operation would be safe.

Cleanup belongs to the script's design

A short-lived temporary directory can be paired with an exit trap:

bash
bash -c 'study_tmp=$(mktemp -d) || exit 1; trap '\''rmdir "$study_tmp"'\'' EXIT; printf "temporary directory: %s\n" "$study_tmp"'

The trap removes this empty directory when that Bash process exits normally. It cannot run after SIGKILL or a machine failure; “guaranteed cleanup” would be too strong. Larger scripts also need decisions about bounded retries, duplicate runs, and logging. Those are optional extensions after the core input/status tests.

Keep the diagnostic and tests for the next lab. Remove usage.out and usage.err after inspecting them. If a test wraps a real system-changing command later, substitute a controlled fake executable or use a disposable environment; a unit test should not restart an unrelated service.

Apply this to MicroBank

Define distinct outcomes for the future MicroBank diagnostic: invalid input, missing evidence file, failed local page request, and successful expected response. Reuse the small test style with controlled fixtures. A test must detect a deliberate regression, and a successful fixture probe must not be labelled a successful deposit or database recovery.

Keep this observation in the MicroBank readiness project.

Sources

Primary references: Bash exit status↗; ShellCheck project↗.

Prove what you learned

Inspect the result

Record input, stdout/stderr, exit status and an assertion for the selected case.

Explain it in your own words

Explain why your observation supports the stated outcome, and name one thing it does not prove.

Try a small variation

Add a path containing spaces or an empty-input case, predict the result, and keep cleanup scoped.

Your evidence should show

  • Record the context/revision or label the work as a paper or prepared fixture.
  • Compare the prediction with the actual result from “An exit status is a result the caller can use”.
  • Record input, stdout/stderr, exit status and an assertion for the selected case.
  • Explain the transfer challenge and retain the same verification question.
Hint

Trace arguments, quoting, exit status and the point where results are captured.

Answer guide: compare after predicting

Yes. Verify inputs and exit behaviour with the declared cases; output text alone is insufficient.

Compare this with your actual observation. A paper answer is useful reasoning practice; record execution separately.

Recall the permission and path checks the diagnostic depends on. Revisit the previous lesson.

Save the prediction, result and explanation ↓

Your notes and evidence

Record observations, questions, or links to your work. Keep credentials out of your notes.

Loading saved progress…

Back up or restore this path

Progress and notes stay in this browser. A backup contains only this learning path.