Jesse A. Green / notebook

Live demo: the DocCompiler guide library as a static, read-only snapshot (October 2026). No backend, no accounts, nothing to submit.

ShellCheck Administrator & Developer Guide

Generated June 2026 from koalaman/shellcheck at 764802b

AI-generated documentation. The library is checked by sampling (three repositories so far); this guide was not individually reviewed. Verify against the project before relying on it.

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:

  1. Parse-level comments (prComments) — produced by ShellCheck.Parser while building the AST. These are syntax errors (the SC10xx family) detected during parsing.
  2. Analysis comments (arComments) — produced by ShellCheck.Analyzer by walking the completed AST. These are the semantic and stylistic checks (the SC2xxx/SC3xxx families).

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:

  1. --shell / -s flag (csShellTypeOverride)
  2. # shellcheck shell=... directive
  3. Shebang line
  4. Filename extension: .ksh→Ksh, .bash/.bats/.envrc→Bash, .dash→Dash
  5. Fallback to Bash, emitting SC2148 if 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 an IORef-backed map; stdin is -)
  • siFindSource — resolve a source/. target, honoring --source-path, SCRIPTDIR, and the external-sources policy
  • siGetConfig — 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.hs is the runner that invokes each module's runTests. 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_64 Docker 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:

  1. SHELLCHECK_OPTS environment variable — flags split on spaces and prepended to the argument list on every invocation (getEnvArgs in shellcheck.hs).
  2. .shellcheckrc — discovered per script (see §7.3).
  3. In-file # shellcheck directives — file-wide (before the first command) or scoped to the following command/block.
  4. 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 yields SC1144). 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):

  1. Starting from the script's directory, walk up through every parent directory looking for .shellcheckrc or shellcheckrc. The first match wins.
  2. If none found, try the user home config: ~/.shellcheckrc, then $XDG_CONFIG_HOME/shellcheckrc (usually ~/.config/shellcheckrc) on Unix, or %APPDATA%/shellcheckrc on Windows.
  3. --rcfile PATH bypasses discovery entirely; --norc disables 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 .shellcheckrc may 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.

  • [ ] Install a pinned version everywhere (don't track stable/latest in CI).
  • [ ] Commit a project .shellcheckrc so every developer and the CI server share one config.
  • [ ] Set a severity gate in CI appropriate to the codebase's maturity (--severity=error for legacy, style for greenfield).
  • [ ] Add a pre-commit hook so issues are caught before review.
  • [ ] Pair ShellCheck with shfmt for 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 exit 2/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:

  1. Bump the pinned version in one branch.
  2. Run a full scan and review newly-surfaced warnings.
  3. Fix or disable them 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

  1. Choose the right module: general checks go in Analytics.hs; command-name-specific checks in Checks/Commands.hs; control-flow-dependent checks in Checks/ControlFlow.hs; portability checks in Checks/ShellSupport.hs. Organization-private checks belong in the otherwise-empty Checks/Custom.hs.
  2. Allocate the next SC code with the nextnumber maintainer script.
  3. Implement the check against the AST (ShellCheck.AST / ShellCheck.ASTLib).
  4. Add prop_* QuickCheck properties in the same module.
  5. If it should be opt-in, add a CheckDescription to that module's optionalChecks.

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-sources in CI, ShellCheck will read any file a script references via source. 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 crafted source paths.


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. SystemInterface cleanly 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 (semigroups for ghc < 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.hs imports Debug.Trace with a -- DO NOT SUBMIT marker and uses traceShowId in one property (prop_fileCanSuppressDfa) — leftover debugging that should be removed.
  • A -- TODO: Clean up the cruft this is layered on sits atop analyzeScript, acknowledging accumulated complexity in the analyzer plumbing.
  • A FIXME in Checker.hs notes that recursively-sourced files should emit SC1093 ("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.Trace import and traceShowId/DO NOT SUBMIT marker in Checker.hs.
  • Implement recursive-source detection (SC1093). Close the documented FIXME so infinite/recursive source chains are reported instead of silently ignored.
  • Reduce false positives raised by the community, e.g. SC2030/SC2031 on $_ (#3478), SC2218 false positives (#3421), SC2183 with printf %()T (#3446), and SC1075's severity (#3472).
  • Broaden set -e awareness (#3444) — warn when scripts can fail due to set -e interactions.
  • Performance for large generated scripts — extended analysis is already toggleable; further profiling of CFGAnalysis would 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.)