ShellCheck flags a line in your bash script that you have decided is genuinely fine. How do you suppress that one finding without turning the check off repository-wide, what scope does the suppression comment have, and when is suppressing one legitimate?
answer
- narrowest scope that works
- comment sits above the command
- before the first command means file-wide
- policy decisions go in .shellcheckrc
- a disable with no reason is the smell
basics
~20 sPut a # shellcheck disable=SC2086 comment on its own line immediately above the offending command, with a comment saying why. Placed before the first command it silences the whole file instead; repo-wide suppression belongs in a .shellcheckrc.
solid answer
~50 sYou annotate the single line: a comment of the form `# shellcheck disable=SC2086` on its own line directly above the command suppresses that code for that command only. Scope is positional — the directive attaches to the next command, function or block, and if you place it before the first command in the file it becomes file-wide, which is almost never what you want on a long script. Repository-wide decisions go in a `.shellcheckrc` with a `disable=` line, or `--exclude=` on the command line. A suppression is legitimate when the finding is a genuine false positive — deliberate word splitting of an option string, a variable you know is set by a sourced file, `SC2016` where you really do want a literal `$` — and it is illegitimate when it is just cheaper than the fix. The convention that makes this reviewable is a justification comment next to the directive; a disable with no reason is the smell reviewers look for.
go deeper
Know the syntax and the placement: # shellcheck disable=SC2086 on its own line directly above the command it applies to, not at the end of that line.
Explain the three scopes — command, file, repository — and that a directive before the first command silences the whole file. Say which mechanism belongs to which kind of decision.
Demonstrate the judgment: distinguish a genuine false positive from a fix you skipped, and insist on a written justification so the suppression can be reviewed and later revisited.
Own the policy angle: what goes in the shared .shellcheckrc versus what stays local, how you keep the suppression count from being a one-way ratchet, and how you audit it.
## The three scopes ShellCheck suppressions come in three widths, and choosing the narrowest one that works is the whole skill. **One command.** A directive comment on its own line, immediately before the command, applies to that command: ```bash # Word splitting is intended: EXTRA_ARGS holds several flags. # shellcheck disable=SC2086 mytool $EXTRA_ARGS "$input" ``` The directive attaches to the next *command* — so if the next thing is a compound statement such as a `while` loop or a function definition, it covers that whole block, which can silently hide a second, unrelated instance of the same code inside it. When precision matters, put the directive immediately above the exact simple command. **Whole file.** The same comment placed *before the first command* — conventionally just under the shebang — applies to the entire file: ```bash #!/usr/bin/env bash # shellcheck disable=SC2034 # vars here are consumed by the sourcing script set -euo pipefail ``` This is the right scope for a config-style library whose variables are genuinely used elsewhere, and the wrong scope for a 400-line script where it will mask a real instance a year from now. **Whole repository.** A `.shellcheckrc` file in the project root (or a parent directory) carries global settings, one per line: ``` shell=bash disable=SC2312 source-path=SCRIPTDIR external-sources=true ``` The command-line equivalents are `--exclude=SC2086` (`-e`) and `--enable=` for the optional checks. Repo-level disabling is a policy decision — record it where the whole team sees it, not scattered across files. Multiple codes go in one directive, comma-separated: `# shellcheck disable=SC2086,SC2046`. A trailing comment on the same line as the command is *not* a directive; ShellCheck only recognises the directive comment on its own line preceding the target. ## When suppression is legitimate The honest cases share a shape: ShellCheck's rule is right in general and wrong here, and you can say why in one sentence. - **Deliberate word splitting.** `mytool $FLAGS` where `FLAGS` holds several options and you have accepted the tradeoff (the alternative — an array — is usually the better fix, and is what a reviewer will suggest). - **Variables assigned elsewhere.** `SC2154` ("referenced but not assigned") on a variable that a sourced library or a `source`d config sets. Better: point ShellCheck at the source file so it can see the assignment. - **Intentionally literal `$`.** `SC2016` on `awk '{print $1}'` or a single-quoted template string. - **A rule you disagree with as a team**, such as a purely stylistic optional check — but that belongs in `.shellcheckrc`, not in a file. And the illegitimate case, which is most of them in practice: the finding is real, the fix is quoting or an `|| exit`, and the directive is simply faster. `SC2164` (unchecked `cd`) and `SC2086` (unquoted expansion) suppressed without a reason are the two a reviewer should push back on hardest, because both are exactly the silent-wrong failure the linter exists to catch. ## Make it reviewable The operational rule that keeps a codebase honest is: **every disable carries a justification comment**. The directive states the *what*; the comment next to it states the *why*. This turns a suppression into something a reviewer can agree or disagree with, and makes `grep -rn 'shellcheck disable' .` a useful audit — you can read the list and see which reasons have expired. A related trick: prefer fixing the root cause so the directive is not needed at all. `SC2034` usually means the variable really is unused; `SC2154` usually means the source path should be declared; `SC2155` is fixed by splitting `local x` from `x=$(cmd)`. A repository whose suppression count keeps climbing is not one where ShellCheck is wrong more often — it is one where the linter has stopped being enforced. ## Interview framing Saying "I'd add `# shellcheck disable`" is a weak answer on its own. The strong version is the ladder: fix it if it is a real bug; narrow the scope to one command if it is a genuine false positive; put it in `.shellcheckrc` only if it is a team-wide policy; and always leave the reason in a comment.
- What happens if you put the disable comment just under the shebang on a 300-line script?It becomes a file-wide suppression, because a directive placed before the first command applies to the whole file. That silences every instance of the code in the script, including ones added later that are real bugs. Reserve file-wide scope for files where the exemption genuinely applies throughout, such as a sourced config whose variables are all consumed elsewhere.
- How would you review a pull request that adds five `# shellcheck disable=SC2086` directives?Ask what each one is for. SC2086 is unquoted expansion — the code path ShellCheck exists to protect. If the intent is to pass several arguments, the correct fix is an array expanded as `"${args[@]}"`, not a suppression. I would accept a directive only where the author can state a specific reason in a comment next to it.
- Is there a way to see every suppression a repository currently carries?Yes — the directives are ordinary comments, so `grep -rn 'shellcheck disable' .` enumerates them, plus whatever `disable=` lines the `.shellcheckrc` holds. Doing that periodically is worthwhile: suppressions accumulate silently, and a directive whose justification has expired is indistinguishable from one that is still valid until someone reads it.
saying these in an interview costs you the question
- Add the directive as a trailing comment on the same line
- Just put disable at the top of the file, it is simpler
- Disable SC2086 globally, quoting is a style preference
- Any warning you disagree with can be suppressed without a reason
- A .shellcheckrc entry and a per-line directive are interchangeable