Skip to content
← System engineer foundations

Learning bite

Exit codes, linting, and tests

Exercise success and failure paths in small diagnostic scripts.

Documentation reviewed2026-10-01 · 3 min read
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.

Sources

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

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.