This guide is the operational and architectural reference for ShellCheck — the GPLv3 static analysis tool for shell scripts. It is written for the people who deploy ShellCheck across a team or CI fleet, build it from source, integrate it into tooling, and extend or contribute to it. For end-user CLI usage, see the companion User Guide.
ShellCheck is a Haskell program of about 700 KB of source across ~30 modules. It parses a shell script into an abstract syntax tree, runs a battery of analyzers over that tree, filters the results by severity and user directives, and renders them through a pluggable formatter. This document covers every layer of that pipeline.
All facts here are derived from the ShellCheck source at version 0.11.0, commit 764802b.
1. Executive Summary
| Attribute | Value |
|---|---|
| Project | ShellCheck — shell script static analysis tool |
| Version | 0.11.0 |
| Language | Haskell (Haskell2010, GHC 8.0 – 9.6.6) |
| License | GNU GPL v3 or later |
| Build systems | Cabal (primary) and Stack |
| Primary artifact | shellcheck — a single self-contained binary |
| Distributed as | Static Linux binaries, macOS/Windows binaries, ~20 package managers, Docker images |
| External runtime deps | None (statically linked binary) |
| Source size | ~701 KB across 84 files; library is 15 modules in src/ShellCheck/ |
| Homepage | https://www.shellcheck.net |
What it is: a linter that catches shell-scripting bugs a shell would only reveal at runtime — quoting mistakes, broken conditionals, portability hazards, and dangerous constructs. It gives shell-aware advice, tailoring warnings to sh, bash, dash, ksh, or busybox.
What it is not: a formatter (it deliberately ignores indentation and style — pair it with shfmt), and not a zsh/fish checker.
Operational profile: ShellCheck is a stateless, single-shot CLI. There is no server, no daemon, no database, no network access at runtime. "Operating" it means installing the binary, pinning its version, providing configuration (.shellcheckrc / SHELLCHECK_OPTS), and gating builds on its exit code.
2. Architecture
2.1 High-level pipeline
ShellCheck processes each file through a strict pipeline. The CLI front-end (shellcheck.hs) handles options and I/O; the library (src/ShellCheck/) does the analysis.
shellcheck.hs (CLI front-end)
|
getArgs + SHELLCHECK_OPTS ─► parseArguments ─► Options
|
per file: siReadFile
|
v
ShellCheck.Checker.checkScript (orchestrator)
|
┌─────────────────────┼──────────────────────┐
v v v
ShellCheck.Parser ShellCheck.Analyzer severity / include /
parseScript analyzeScript exclude / directive
(parsec → AST, (run all checkers filtering
parse comments, over the AST) (shouldInclude)
token positions) |
| |
└─────────► comments + AST ◄──────────┘
|
v
Formatter (tty | gcc | checkstyle |
diff | json | json1 | quiet)
|
v
stdout + exit code (0–4)
The orchestration is visible in ShellCheck.Checker.checkScript: it calls parseScript to obtain the AST and parse-level comments, builds an AnalysisSpec, calls analyzeScript, then merges parse comments with analysis comments, de-duplicates (nub), sorts by position/severity/code, and filters.
2.2 The two comment sources
ShellCheck issues come from two stages, and both feed the same output:
- Parse-level comments (
prComments) — produced byShellCheck.Parserwhile building the AST. These are syntax errors (theSC10xxfamily) detected during parsing. - Analysis comments (
arComments) — produced byShellCheck.Analyzerby walking the completed AST. These are the semantic and stylistic checks (theSC2xxx/SC3xxxfamilies).
Parse comments use raw token IDs; tokenToPosition translates them into file/line/column positions using the parser's prTokenPositions map before they're merged with analysis comments.
2.3 The analyzer and its five checkers
ShellCheck.Analyzer.analyzeScript runs five independent checker modules over the AST and concatenates their findings (mconcat), then applies filterByAnnotation to honor in-script disable/enable directives:
| Checker module | Responsibility |
|---|---|
ShellCheck.Analytics |
The main body of checks — quoting, conditionals, common mistakes, robustness |
ShellCheck.Checks.Commands |
Per-command checks keyed on the command name (e.g. misuse of find, grep, printf, which) |
ShellCheck.Checks.ControlFlow |
Control-flow / dataflow-graph based checks (unreachable code, masked returns) |
ShellCheck.Checks.Custom |
A deliberately empty hook for organizations to add their own checks |
ShellCheck.Checks.ShellSupport |
Portability — flags constructs unsupported by the target shell dialect |
Each checker is a function of AnalysisSpec → Parameters → [TokenComment]. They are combined as monoids, which is how ShellCheck stays modular: adding a checker means adding one entry to the checkers list in Analyzer.hs.
2.4 Optional checks
Three of the five checkers (Analytics, Checks.Commands, Checks.ControlFlow) also expose an optionalChecks list of opt-in checks that are off by default. Analyzer.optionalChecks concatenates them; shellcheck.hs's --list-optional prints them via printOptional. Each is a CheckDescription with a cdName, cdDescription, and positive/negative examples. There are 11 in this version (see §7.2).
2.5 Dataflow (extended) analysis
ShellCheck.CFG builds a control-flow graph (using the fgl graph library), and ShellCheck.CFGAnalysis performs dataflow analysis over it. This is what catches issues like code that can never execute after an exit. It is on by default but can be disabled per-run (--extended-analysis=false), per-rc-file, or per-file (# shellcheck extended-analysis=false), because it is the most CPU- and memory-intensive part of the pipeline on very large scripts.
2.6 Shell-dialect resolution
ShellCheck.Checker.shellFromFilename and the option/directive/shebang logic together resolve the dialect in this priority order:
--shell/-sflag (csShellTypeOverride)# shellcheck shell=...directive- Shebang line
- Filename extension:
.ksh→Ksh,.bash/.bats/.envrc→Bash,.dash→Dash - Fallback to Bash, emitting
SC2148if there was no shebang
.sh is intentionally not in the extension map — it's too generic to imply a dialect — so a .sh file with no shebang triggers SC2148.
2.7 Source resolution and the SystemInterface
All file access is abstracted behind a SystemInterface record (ShellCheck.Interface), constructed in shellcheck.hs's ioInterface. This is the seam that makes ShellCheck testable and that powers https://www.shellcheck.net (which supplies a non-filesystem interface).
It carries three operations:
siReadFile— read a script (with caching via anIORef-backed map; stdin is-)siFindSource— resolve asource/.target, honoring--source-path,SCRIPTDIR, and the external-sources policysiGetConfig— locate and read the applicable.shellcheckrc
The allowable predicate enforces the external-sources security policy: by default only files named as input (plus /dev/null) are followed; -x/--external-sources or external-sources=true widens this.
3. Repository Structure
shellcheck/
├── shellcheck.hs CLI entry point (main-is); option parsing, I/O, formatter dispatch
├── shellcheck-dev.hs Dev-only helper (cabal -fdev-mode); `ast` command to dump a parse tree
├── ShellCheck.cabal Package definition: deps, modules, executables, test suite
├── stack.yaml Stack build config (resolver lts-18.15)
├── shellcheck.1.md The man page (pandoc markdown) — authoritative CLI reference
├── README.md Project overview, install matrix, "gallery of bad code"
├── CHANGELOG.md Release history
├── Dockerfile.multi-arch Multi-architecture container build
├── manpage / nextnumber / Maintainer helper scripts (build man page, allocate next SC code,
│ quickrun / quicktest / fast local run/test, strip tests, set git version)
│ setgitversion / striptests
├── src/ShellCheck/ The library (all analysis logic)
│ ├── AST.hs Abstract syntax tree types for shell scripts
│ ├── ASTLib.hs Helpers for traversing/querying the AST
│ ├── Parser.hs parsec-based shell parser → AST (+ SC10xx parse errors)
│ ├── Analyzer.hs Runs the 5 checkers; exposes optionalChecks
│ ├── Analytics.hs The largest checker — most SC2xxx checks
│ ├── AnalyzerLib.hs Shared analysis utilities, Parameters, token plumbing
│ ├── Checker.hs Top-level checkScript orchestration + filtering
│ ├── CFG.hs Control-flow graph construction (fgl)
│ ├── CFGAnalysis.hs Dataflow analysis over the CFG
│ ├── Checks/
│ │ ├── Commands.hs Per-command-name checks
│ │ ├── ControlFlow.hs CFG/dataflow-based checks
│ │ ├── Custom.hs Empty extension hook for custom checks
│ │ └── ShellSupport.hs Shell-dialect portability checks
│ ├── Formatter/
│ │ ├── Format.hs Formatter interface (header/onResult/onFailure/footer)
│ │ ├── TTY.hs Default human-readable output
│ │ ├── GCC.hs gcc-compatible one-line-per-issue output
│ │ ├── CheckStyle.hs Checkstyle XML
│ │ ├── Diff.hs Unified-diff auto-fixes
│ │ ├── JSON.hs Legacy JSON (tab stop 8)
│ │ ├── JSON1.hs Compact JSON (tab stop 1)
│ │ └── Quiet.hs No output; exit code only
│ ├── Interface.hs Core data types: CheckSpec, CheckResult, Severity, Shell,
│ │ SystemInterface, Comment, PositionedComment, Fix
│ ├── Fixer.hs Applies/merges fixes (powers the diff format)
│ ├── Data.hs Static data: version string, constants
│ ├── Regex.hs regex-tdfa wrappers
│ └── Prelude.hs Small custom prelude
├── builders/ Per-target Docker cross-build definitions
│ ├── linux.{x86_64,aarch64,armv6hf,riscv64}/
│ ├── darwin.{x86_64,aarch64}/
│ └── windows.x86_64/
├── snap/snapcraft.yaml Snap package definition
└── test/ Build/release/distro integration test scripts + test runner
Note
The unit tests are inline with the source as QuickCheck properties (functions prefixed
prop_), not in a separate test tree.test/shellcheck.hsis the runner that invokes each module'srunTests. See §9.
4. System Requirements
4.1 Runtime
The released shellcheck binary is statically linked and has no runtime dependencies — drop it on the PATH and run it. The Linux release binaries in particular are fully static, so they run on any modern Linux without library concerns.
| Resource | Requirement |
|---|---|
| OS | Linux, macOS, Windows, *BSD (anywhere a binary is published or it compiles) |
| Disk | A few MB for the binary |
| Memory | Trivial for normal scripts; can grow with extended analysis on very large (2000+ line) files |
| Network | None |
4.2 Build-time
Compiling from source requires a Haskell toolchain and ~2 GB of RAM.
| Tool | Detail |
|---|---|
| GHC | Tested with 8.0, 8.10, 9.0.1, 9.4.7, 9.6.6 |
| Cabal | cabal-install (primary build path) |
| Stack | Optional alternative; stack.yaml pins resolver lts-18.15 |
| pandoc | Only needed to regenerate the man page from shellcheck.1.md |
5. Building From Source
5.1 With Cabal (primary)
git clone https://github.com/koalaman/shellcheck.git
cd shellcheck
cabal update
cabal install
This compiles ShellCheck and installs the binary to ~/.cabal/bin. Add it to your PATH:
export PATH="$HOME/.cabal/bin:$PATH"
which shellcheck # → ~/.cabal/bin/shellcheck
5.2 With Stack
stack update
stack install ShellCheck # installs to ~/.local/bin
5.3 Build the man page
The distro packages ship a man page; if you build from source, regenerate it with pandoc:
pandoc -s -f markdown-smart -t man shellcheck.1.md -o shellcheck.1
sudo mv shellcheck.1 /usr/share/man/man1
5.4 Cabal flags and executables
ShellCheck.cabal defines:
| Component | Notes |
|---|---|
library |
The 15 src/ShellCheck/ modules (exposed) + Paths_ShellCheck |
executable shellcheck |
main-is: shellcheck.hs, links the library |
executable shellcheck-dev |
Buildable only with -fdev-mode; exposes ShellCheck.Debug and an ast dump command for AI/maintainer use |
test-suite test-shellcheck |
exitcode-stdio-1.0, main-is: test/shellcheck.hs |
flag dev-mode |
Manual, default False; gates the dev executable and Debug module |
# Build and run the dev AST dumper
cabal run -fdev-mode shellcheck-dev -- ast 'echo $foo'
5.5 Cross-compilation and packaging
The builders/ tree contains per-target Docker build definitions (Dockerfile, build, tag) for Linux x86_64/aarch64/armv6hf/riscv64, macOS x86_64/aarch64, and Windows x86_64. Dockerfile.multi-arch and the builders/build_builder/run_builder scripts orchestrate the release matrix. snap/snapcraft.yaml builds the Snap package.
Tip
To reproduce the official static Linux binary, use the
builders/linux.x86_64Docker definition rather than a host cabal build — it pins the toolchain and produces the statically-linked artifact distributed on GitHub Releases.
6. Dependencies
ShellCheck's library depends on a focused set of Haskell packages (from ShellCheck.cabal). Version bounds are based on GHC 7.10.3 (lower) through 9.12.1 (upper).
| Package | Role in ShellCheck |
|---|---|
parsec |
The parser combinator library that powers ShellCheck.Parser |
regex-tdfa |
Regular expression engine behind ShellCheck.Regex |
fgl |
Functional Graph Library — backs the control-flow graph (CFG/CFGAnalysis) |
aeson |
JSON serialization for the json/json1 formatters |
Diff |
Diff generation for the diff (auto-fix) formatter |
containers |
Map/Set used throughout (token maps, caches) |
array |
Efficient arrays in analysis |
mtl / transformers |
Monad transformer stacks (ExceptT, etc. in the CLI and analysis) |
deepseq |
Forcing evaluation (seq on file contents) |
filepath / directory |
Path manipulation and config-file discovery (getXdgDirectory needs directory ≥ 1.2.3) |
bytestring |
Binary-safe file I/O |
base |
Standard library |
QuickCheck |
Property-based testing (the inline prop_ checks) |
semigroups |
Only for ghc < 8.0 compatibility |
Important
There are no runtime services or native libraries here — every dependency is a pure Haskell package compiled into the binary. The consumer-facing supply-chain surface of the distributed binary is effectively zero, which is a strong security posture worth noting in any vendor review.
7. Configuration Mastery
ShellCheck has four configuration surfaces. Understanding their precedence is the key operational skill.
7.1 Precedence model
From lowest to highest priority:
SHELLCHECK_OPTSenvironment variable — flags split on spaces and prepended to the argument list on every invocation (getEnvArgsinshellcheck.hs)..shellcheckrc— discovered per script (see §7.3).- In-file
# shellcheckdirectives — file-wide (before the first command) or scoped to the following command/block. - Command-line flags — the final word.
Two settings have explicit override semantics encoded in the code:
--extended-analysis(flag) overrides both directives and rc files.--external-sources: a flag or rc setting can enable it; a file directive can only disable it, never enable it (attempting to enable via directive yieldsSC1144). This is a security guard rail — a checked script cannot grant itself permission to read arbitrary files.
7.2 Optional checks
Enable with --enable=NAME,… (or all), or enable=NAME in .shellcheckrc. List them with --list-optional.
| Name | Description | Bad → Good |
|---|---|---|
quote-safe-variables |
Suggest quoting variables without metacharacters | echo $var → echo "$var" |
avoid-nullary-conditions |
Suggest explicitly using -n in [ $var ] |
[ "$var" ] → [ -n "$var" ] |
avoid-negated-conditions |
Suggest removing unnecessary comparison negations | [ ! "$var" -eq 1 ] → [ "$var" -ne 1 ] |
add-default-case |
Suggest adding a default case in case statements |
add *) … ;; |
require-variable-braces |
Suggest braces around all variable references | echo $var → echo ${var} |
check-unassigned-uppercase |
Warn when uppercase variables are unassigned | echo $VAR → VAR=hello; echo $VAR |
require-double-brackets |
Require [[ ]] and warn about [ ] in Bash/Ksh |
[ -e /etc/issue ] → [[ -e /etc/issue ]] |
check-set-e-suppressed |
Notify when set -e is suppressed during function invocation |
use func; echo ok patterns carefully |
check-extra-masked-returns |
Flag additional cases where exit codes are masked | split assignment from rm |
useless-use-of-cat |
Detect Useless Use Of Cat (UUOC) | cat foo | grep bar → grep bar foo |
deprecate-which |
Suggest command -v instead of which |
which javac → command -v javac |
7.3 .shellcheckrc discovery
The discovery algorithm (in shellcheck.hs's getConfig/getConfigPaths):
- Starting from the script's directory, walk up through every parent directory looking for
.shellcheckrcorshellcheckrc. The first match wins. - If none found, try the user home config:
~/.shellcheckrc, then$XDG_CONFIG_HOME/shellcheckrc(usually~/.config/shellcheckrc) on Unix, or%APPDATA%/shellcheckrcon Windows. --rcfile PATHbypasses discovery entirely;--norcdisables it.
A broken rc file does not abort the run — ShellCheck emits SC1134 and continues.
7.4 Directive and rc key reference
| Key | Scope | Effect |
|---|---|---|
disable |
command/block/file | Disable codes (comma list, ranges SCxxxx-SCyyyy, or all) |
enable |
file-wide only | Enable an optional check by name |
extended-analysis |
file/rc | true/false dataflow toggle |
external-sources |
rc (enable), directive (disable only) | Allow opening arbitrary sourced files |
source |
command | Override the path a source/. reads (or /dev/null to skip) |
source-path |
command/file/rc | Add a search directory (SCRIPTDIR = script's dir); accumulates |
shell |
file-wide | Override detected shell dialect |
Note
Values in
.shellcheckrcmay be quoted with'or"(since 0.9.0) to allow spaces, e.g.source-path="My Documents/scripts".
8. Operations & Deployment
ShellCheck is not a service, so "operations" means standardizing how a team runs it.
8.1 Recommended fleet setup
- [ ] Install a pinned version everywhere (don't track
stable/latestin CI). - [ ] Commit a project
.shellcheckrcso every developer and the CI server share one config. - [ ] Set a severity gate in CI appropriate to the codebase's maturity (
--severity=errorfor legacy,stylefor greenfield). - [ ] Add a pre-commit hook so issues are caught before review.
- [ ] Pair ShellCheck with
shfmtfor formatting, since ShellCheck intentionally ignores it.
8.2 CI gate pattern
#!/usr/bin/env bash
set -euo pipefail
# Fail the pipeline on any error- or warning-level issue across all scripts
find . -type f -name '*.sh' -print0 \
| xargs -0 shellcheck --severity=warning --format=gcc
ShellCheck's exit codes drive the gate (see the User Guide §11). In short: 0 = clean, 1 = issues found, 2/3/4 = operational/usage errors (treat these as "ShellCheck itself failed," distinct from "the script has lint").
Tip
Distinguish exit
1(lint found — fix the script) from exit2/3/4(file missing / bad flag / bad formatter — fix the invocation). In a wrapper script, branch on$?so a typo in a flag doesn't masquerade as a "passing" or "failing" lint result.
8.3 Containerized invocation
docker run --rm -v "$PWD:/mnt" koalaman/shellcheck:v0.11.0 --severity=warning myscript
Remember the container can only see mounted paths — mount the project root and put .shellcheckrc inside it, since ~/.shellcheckrc on the host is invisible to the container.
8.4 Upgrades
New releases add new checks. Schedule upgrades deliberately:
- Bump the pinned version in one branch.
- Run a full scan and review newly-surfaced warnings.
- Fix or
disablethem in.shellcheckrc, then merge.
This avoids a "green yesterday, red today" surprise across the whole team.
9. Testing
ShellCheck's tests are QuickCheck properties co-located with the code they test. Each module defines prop_* functions and a runTests (built with Template Haskell's $quickCheckAll); test/shellcheck.hs aggregates and runs them.
cabal test
# or, for fast local iteration, the maintainer scripts:
./quicktest
The property style means a check and its tests live together — for example ShellCheck.Checker carries dozens of prop_* properties asserting exact code lists for given scripts (check "echo $1" == [2086]). When adding or modifying a check, add prop_ properties in the same module asserting which codes a snippet should and shouldn't produce.
test/ also holds integration scripts: buildtest, check_release, distrotest, and stacktest validate packaging and distro builds rather than analysis logic.
10. Extending ShellCheck
10.1 Adding a check
- Choose the right module: general checks go in
Analytics.hs; command-name-specific checks inChecks/Commands.hs; control-flow-dependent checks inChecks/ControlFlow.hs; portability checks inChecks/ShellSupport.hs. Organization-private checks belong in the otherwise-emptyChecks/Custom.hs. - Allocate the next
SCcode with thenextnumbermaintainer script. - Implement the check against the AST (
ShellCheck.AST/ShellCheck.ASTLib). - Add
prop_*QuickCheck properties in the same module. - If it should be opt-in, add a
CheckDescriptionto that module'soptionalChecks.
10.2 Adding an output format
Implement the Formatter interface from ShellCheck.Formatter.Format (the header/onResult/onFailure/footer callbacks), add the module under Formatter/, register it in the formats map in shellcheck.hs, and list it in the cabal exposed-modules.
10.3 The SystemInterface seam
Because all I/O goes through SystemInterface, you can embed ShellCheck's library in another Haskell program and supply scripts from memory, a database, or a network request — this is exactly how shellcheck.net works. mockedSystemInterface (used throughout the tests) shows the pattern.
10.4 Contributing
Contributions go through GitHub pull requests and must be licensed under GPLv3 (the contributor retains copyright). See the DevGuide on the wiki. Long-form descriptions of every check live at https://github.com/koalaman/shellcheck/wiki/SC<NNNN>.
11. Security Analysis
| Concern | Assessment |
|---|---|
| Untrusted input | ShellCheck's lineage is a public web service checking untrusted scripts, so it's hardened against malicious input scripts — it parses, never executes them. |
| External sources | The default external-sources=false is a deliberate guard: ShellCheck won't open files a script sources unless explicitly allowed. A checked script cannot enable external sourcing via its own directive (SC1144) — only the operator can, via flag or rc. |
| No code execution | ShellCheck performs pure static analysis. It does not run, source, or eval the scripts it checks. |
| No network | The binary makes no network calls. (The wiki links in output are just text.) |
| Supply chain | Statically-linked binary with only compiled-in Haskell deps; zero runtime dependency surface for consumers. |
| File access | Limited to files named as input plus resolved source targets within policy. Path resolution is canonicalizePath-based with safe fallbacks. |
| Encoding safety | Files are leniently decoded as UTF-8 with ISO-8859-1 fallback (decodeString), avoiding crashes on malformed bytes. |
Caution
When you enable
--external-sourcesin CI, ShellCheck will read any file a script references viasource. On a build agent that checks untrusted third-party scripts, prefer the default (input files only) so a hostile script can't coax ShellCheck into reading sensitive files via craftedsourcepaths.
12. Code Quality & Conventions
- Modular monoid checkers. Checks are combined with
mconcat/<>; adding one is a one-line registration. The analyzer is a pipeline of independent passes. - Abstraction at the I/O boundary.
SystemInterfacecleanly separates pure analysis from effectful file access, enabling both testing and the web service. - Tests beside code. QuickCheck
prop_*properties live in the module they exercise, asserting exact diagnostic code lists — a precise regression net. - Haskell2010, broad GHC support. The codebase compiles across GHC 8.0–9.6+ with conditional compatibility shims (
semigroupsforghc < 8.0). - Single static binary. Distribution is dead simple — no interpreter, no shared libraries.
Observed rough edges (from source comments) worth a maintainer's attention:
Checker.hsimportsDebug.Tracewith a-- DO NOT SUBMITmarker and usestraceShowIdin one property (prop_fileCanSuppressDfa) — leftover debugging that should be removed.- A
-- TODO: Clean up the cruft this is layered onsits atopanalyzeScript, acknowledging accumulated complexity in the analyzer plumbing. - A
FIXMEinChecker.hsnotes that recursively-sourced files should emitSC1093("recursively sourced") but currently don't (prop_noInfiniteSourcing).
13. Potential Improvements
Drawn from the codebase and the project's open issues:
- Remove debug residue. Drop the
Debug.Traceimport andtraceShowId/DO NOT SUBMITmarker inChecker.hs. - Implement recursive-source detection (
SC1093). Close the documentedFIXMEso infinite/recursivesourcechains are reported instead of silently ignored. - Reduce false positives raised by the community, e.g.
SC2030/SC2031on$_(#3478),SC2218false positives (#3421),SC2183withprintf %()T(#3446), andSC1075's severity (#3472). - Broaden
set -eawareness (#3444) — warn when scripts can fail due toset -einteractions. - Performance for large generated scripts — extended analysis is already toggleable; further profiling of
CFGAnalysiswould let it stay on for bigger inputs.
Note
Because each check carries its own
prop_*properties, every improvement above should land with property assertions pinning the exact codes produced for representative snippets — that is the project's contribution contract.
14. Quick Reference
14.1 Module map
| Layer | Modules |
|---|---|
| CLI | shellcheck.hs, shellcheck-dev.hs |
| Orchestration | Checker, Analyzer |
| Parsing | Parser, AST, ASTLib |
| Analysis | Analytics, AnalyzerLib, Checks.{Commands,ControlFlow,Custom,ShellSupport}, CFG, CFGAnalysis |
| Output | Formatter.{Format,TTY,GCC,CheckStyle,Diff,JSON,JSON1,Quiet}, Fixer |
| Support | Interface, Data, Regex, Prelude |
14.2 Exit codes
| Code | Meaning |
|---|---|
| 0 | No issues |
| 1 | Issues found |
| 2 | File(s) could not be processed |
| 3 | Bad invocation syntax (e.g. unknown flag) |
| 4 | Bad options (e.g. unknown formatter) |
14.3 Key commands
cabal install # build & install (→ ~/.cabal/bin)
stack install ShellCheck # build & install (→ ~/.local/bin)
cabal test # run the QuickCheck suite
cabal run -fdev-mode shellcheck-dev -- ast 'cmd' # dump a parse tree (dev)
shellcheck --list-optional # list opt-in checks
shellcheck --version # version info
14.4 Configuration surfaces (low → high precedence)
SHELLCHECK_OPTS → .shellcheckrc → in-file directives → CLI flags
(--extended-analysis and --external-sources flags override directives; a file directive can only disable external sources.)