Bash & the POSIX Shell Module 16
Quality: ShellCheck, tests, style and speed interview questions
Interview questions on “Quality: ShellCheck, tests, style and speed”: warm-ups, then what each harder question tests and how a good answer starts.
Warm-up questions
Short questions with short answers, each answered in full.
-
Fresher to mid
What does SC2086 mean, and how do you clear it?
Answered in the lesson ShellCheck: catch bugs before a script runs
-
Fresher to mid
What do $status and $output hold after run, and why not just call the command directly in a @test?
-
Fresher to mid
Why does the style guide prefer $(...) to backticks and [[ ... ]] to [ ... ]?
-
Fresher to mid
Why is $(basename "$f") inside a loop slow, and what replaces it?
Answered in the lesson Fast Bash: builtins, forks and measuring
Questions with model answers
What each question tests, the first step of a good answer and the follow-ups to expect.
-
Mid to senior
A repository's ShellCheck step is green on every developer's machine but red in CI, and only on one script that sources a shared helper. The finding is SC1091. Explain why the two environments disagree and how you would make the CI run green without weakening the check.
- What it tests
- Reading SC1091 as "could not follow the sourced file", knowing what -x, a .shellcheckrc and a source= directive each change, and why a relative path resolves differently in CI.
- First step
- Read the code, not the host. SC1091 means ShellCheck could not read the file a
sourceline points at, so it could not check across the boundary. Ask what differs between a laptop and CI for that path: the working directory the step runs from, whether the helper is checked out, and whether the run uses-xat all. - Follow-ups
-
- Why might a bare shellcheck follow the helper locally yet report SC1091 in CI?
- What does a
# shellcheck source=pathdirective add that-xalone does not? - When is SC1091 the right finding to leave in place rather than resolve?
From the lesson ShellCheck: catch bugs before a script runs
Model answer, Pro. Model answers open with any pass; prices are in the notice on this page.Opening the model answers with your pass…
Model answers open with any MySmartCoPilot pass.
- Pro pass · 1 week₹79$2.99
- 30 days₹179$5.99
- 1 year₹1,699$45
One payment; a pass never renews. The price shown is the total you pay: no fees or charges are added at checkout.
Passes can’t be bought right now, so the model answers can’t be opened at the moment. If you have a pass, enter your licence key.
It also opens every other in-depth part in Learn and every Pro tool. Sales are final, except where the law gives you a right to withdraw, for example in the EU and the UK (refund policy).
In the answer:
- Model answer
- Follow-up answers
- Common mistakes
- Rubric
- Variants
- about 5 minutes
-
Mid to senior
A teammate opens a change that adds
# shellcheck disable=SC2086as the first line of a 200-line script so the build stops failing. Walk through how you decide what is legitimate, and how a team keeps disables honest over months so they do not quietly hide real bugs.- What it tests
- Telling a true false positive from a muted bug, choosing the narrowest scope with a reason, and designing a habit that stops disables from accumulating unseen.
- First step
- Separate the two questions the change mixes up: is any single SC2086 here a real false positive, and is a file-wide disable ever the right scope? Start by asking the author which line splits on purpose and why, then judge each site on its own rather than silencing the whole file at once.
- Follow-ups
-
- Why is a file-top disable almost always the wrong scope, even when one line is a genuine false positive?
- What belongs in the comment beside a disable, and what does its absence cost the next reader?
- How would you find disables that no longer match the code they sit beside, months later?
From the lesson ShellCheck: catch bugs before a script runs
Model answer, Pro.In the answer:
- Model answer
- Follow-up answers
- Common mistakes
- Rubric
- Variants
- about 4 minutes
-
Mid to senior
A Bats test "writes the report" passes when it is the only @test in the file, but fails whenever another test runs before it. The failing message is "report.csv: No such file or directory". Explain what shared state is hiding here, and how you would fix it.
- What it tests
- Knowing what Bats isolates per test and what it does not (the file system, the start folder, what setup_file builds), and choosing between $BATS_TEST_TMPDIR, $BATS_FILE_TMPDIR and the current folder.
- First step
- Ask what the two tests share, because it is not variables or the current folder: each @test runs in a fresh process that starts in the folder bats was started from. What they share is the file system. Find where report.csv is written and whether the earlier test, or its teardown, removes or moves anything on that path.
- Follow-ups
-
- Which per-test, per-file and per-suite folders does Bats give you, and when do you choose each one?
- What one change makes the test independent of the order the file is run in?
- How would you reproduce the failure on demand to prove the fix?
- What rule do you follow in setup and teardown to keep this from coming back?
From the lesson Testing Bash scripts with Bats
Model answer, Pro.In the answer:
- Model answer
- Follow-up answers
- Common mistakes
- Rubric
- Variants
- about 4 minutes
-
Mid to senior
A release script gets a token with curl, then posts the release; after a 503 it runs sleep 5 and posts again, three tries in all. Design an offline Bats suite that proves the retry limit and that the token is sent, without ever sleeping, and show that it would catch a broken retry loop.
- What it tests
- Stubs that keep a call log and answer from a queue, stubbing sleep as well as curl, asserting how often each command ran, and proving those assertions with mutants of the retry loop.
- First step
- Make the fakes keep a record. Put curl and sleep stubs first on PATH in $BATS_TEST_TMPDIR/bin; each appends its arguments as one line to a call log in that folder. The curl stub takes its next answer from a queue file the test writes, such as 200 tok-123, then 503, then 201. A test can then count posts and sleeps in the log as well as read $status.
- Follow-ups
-
- How does the curl stub give a different answer on each call, and why keep its state in a file?
- What exactly do you assert to prove three tries and no more?
- How do you prove that the post sends the token from the first call?
- Which one-line changes to the retry loop should turn a test red, and which test catches each?
From the lesson Testing Bash scripts with Bats
Model answer, Pro.In the answer:
- Model answer
- Follow-up answers
- Common mistakes
- Rubric
- Variants
- about 5 minutes
-
Mid to senior
A 400-line Bash script drives the nightly build. Teammates complain it is hard to read, and two recent bugs came from unquoted expansions. You could reformat and document the script, or you could rewrite it in Python. How do you decide, and in what order do you work?
- What it tests
- Weighing the cost of a rewrite against the cost of maintaining the script, fixing the cheap things first, and reading the guide's 100-line advice as the point to decide.
- First step
- List what makes the script hard to read. Is it formatting (one shfmt -i 2 -ci -w pass fixes that)? Is it missing documentation (add headers and a usage block)? Or is it real complexity: nested loops, parsing JSON, or state that spans many functions? The first two are cheap to fix; the third is a reason to rewrite.
- Follow-ups
-
- What does the style guide say about 100 lines, and how do you read that number?
- How would you prove the rewrite matches the original before you delete the old file?
- What would you tell a manager who asks why you cannot just "clean it up" in an afternoon?
From the lesson Style and maintainable scripts
Model answer, Pro.In the answer:
- Model answer
- Follow-up answers
- Common mistakes
- Rubric
- Variants
- about 4 minutes
-
Mid to senior
Your team agrees on a style guide, but reviews still argue about indentation and braces. Two developers want tabs, two want two spaces, and one quotes every variable while another never does. How do you make the rules enforceable without another round of debate?
- What it tests
- Using shfmt and shellcheck in CI as the single source of truth, turning style into a gate, and understanding that the formatter's output becomes the team's style.
- First step
- Move the decision from people to code. Add shfmt -d, with the team's flags, and shellcheck to the CI job: both must pass before a pull request can merge. The formatter's flags (-i 2 or -i 0 for tabs, -ci for case) become the style, and the argument ends because the gate blocks anything else.
- Follow-ups
-
- How do you handle code that predates the formatter?
- What do you do when ShellCheck is wrong or a rule does not fit your case?
- How does a pre-commit hook change the workflow compared to CI alone?
From the lesson Style and maintainable scripts
Model answer, Pro.In the answer:
- Model answer
- Follow-up answers
- Common mistakes
- Rubric
- Variants
- about 3 minutes
-
Mid to senior
A nightly report script in Bash takes forty minutes to process a two-million-line log file. It used to finish in under a minute. You cannot change the input format. How do you find the slow part, and in what order do you fix it?
- What it tests
- Measuring first with time and TIMEFORMAT before guessing, recognising per-line externals and command substitutions as the usual slow shapes, moving per-line work into one awk pass, and verifying the output is unchanged after each rewrite.
- First step
- Measure before you change anything. Run time ./report.sh on a small sample of the input to confirm the shape of the slowness, then wrap each major phase of the script in time { ...; } with TIMEFORMAT='%6R' and run the full script once. The output names the one or two phases that own the budget, and only those phases are worth looking at next.
- Follow-ups
-
- Which patterns do you expect to find in the slow phase?
- How do you prove the rewrite still produces the same report?
- When would you reach for hyperfine or perf trace instead of the shell's time?
- What do you tell the on-call engineer who has been waiting since 02:00?
From the lesson Fast Bash: builtins, forks and measuring
Model answer, Pro.In the answer:
- Model answer
- Follow-up answers
- Common mistakes
- Rubric
- Variants
- about 5 minutes
-
Mid to staff
A colleague wants every $(cmd) in a 60-line script turned into ${ cmd; }, every $(basename ...) into a parameter expansion and every per-line grep into one awk pass, "for performance". The script runs in CI for 400 milliseconds, twice a day. How do you respond, and when should a script stay as it is?
- What it tests
- Weighing clarity and portability against speed, understanding that a 400 ms script run twice a day has no latency budget to recover, and knowing when to reach for a different language instead of a Bash rewrite.
- First step
- Ask what problem the change is meant to solve. A rewrite always costs reader time; if the script is fast enough and clear, the rewrite trades clarity for a speed-up nobody will notice. Suggest spending the effort on real bugs first (unquoted expansions, missing error handling) and keeping the optimisation for a script that actually hurts.
- Follow-ups
-
- When is the Bash 5.3 ${ cmd; } form worth reaching for, and when is it not?
- What signals that the right fix is to leave Bash entirely and move to Python or Go?
- How do you reply to a colleague who insists every $(...) is "a bug waiting to happen"?
From the lesson Fast Bash: builtins, forks and measuring
Model answer, Pro.In the answer:
- Model answer
- Follow-up answers
- Common mistakes
- Rubric
- Variants
- about 5 minutes
All Bash & the POSIX Shell interview questions · The Bash & the POSIX Shell track