Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Ishellcheck Processor

Purpose

Lints shell scripts with ShellCheck’s analysis, in-process (no shellcheck program, no Haskell runtime to install). The Rust alternative to the shellcheck processor: a port of ShellCheck 0.11.0, so a script gets the same findings with the same codes, positions and messages, and passes or fails the way shellcheck would exit.

How It Works

Each file goes through what ShellCheck does to it: its parser (sh, bash, dash, ksh and busybox dialects, here-documents, arithmetic, [[ ]] conditions, the parse errors and their notes), the control flow graph and its dataflow analysis (which assignments reach which uses, through functions and subshells), and every check of ShellCheck’s Analytics, Commands and ShellSupport modules, optional checks included. The dialect comes from shell, a # shellcheck shell= directive, the shebang or the file extension, in ShellCheck’s order. # shellcheck directives (disable, enable, source, source-path, external-sources, extended-analysis) and .shellcheckrc files (looked up from the script’s directory upwards, then in ~/.shellcheckrc and the XDG config directory) apply as in ShellCheck. Findings are printed as shellcheck --format=gcc prints them, and any finding fails the product:

src/bad.sh:2:1: warning: foo appears unused. Verify use (or export if used externally). [SC2034]
src/bad.sh:3:6: note: Double quote to prevent globbing and word splitting. [SC2086]

Each file is its own product, checked as shellcheck FILE checks it: sourced files are read only with external_sources, and their findings are reported only with check_sourced.

[processor.checker.ishellcheck]
src_dirs = ["scripts"]
exclude = ["SC1091"]
severity = "warning"

Fidelity

Checked against shellcheck 0.11.0 over 4494 real scripts (1850 from the fleet, 2644 from a development machine’s /usr/bin and friends), the 2194 snippets of ShellCheck’s own unit tests, and 28000 mutated copies (broken quotes, brackets and keywords, swapped operators, deleted and duplicated lines): the same findings, line for line (about 360,000 compared), under default options, each shell override, external_sources and check_sourced. Neither side crashes on any of them.

It is also faster and smaller than shellcheck: 2781 real scripts (the fleet’s and a development machine’s) take 2.3 seconds against shellcheck’s 15.7 on 16 threads (18 against 137 seconds of CPU), and the largest script, a 3500-line dkms, takes 1.0 second and 400 MB against 6.4 seconds and 1.9 GB.

The engine’s unit tests (src/engines/shellcheck/tests.rs) keep it that way: they run shellcheck 0.11.0 itself (a missing or different version fails them) and the engine over the scripts in src/engines/shellcheck/testdata/ and deterministic mutants of them, under each option, and require the same output line for line. The processor’s tests check that each TOML field matches the shellcheck option of the same name.

Not supported: output formats other than gcc, auto-fixes (--format=diff), and the --wiki-link-count/--color presentation options.

Source Files

  • Input: .sh and .bash files under src_dirs, or the files in src_files
  • Output: none (checker)

The default src_dirs is empty, so a stanza with no src_dirs or src_files checks nothing: name the directories ([""] for the whole project) or files to check.

Configuration

KeyTypeDefaultDescription
shellstring""The dialect, as --shell (sh, bash, dash, ksh, busybox); empty: from directives, the shebang or the extension
external_sourcesboolfalseFollow sourced files that are not inputs, as --external-sources
source_pathstring[][]Where to look for sourced files, as --source-path (SCRIPTDIR is the script’s directory)
severitystring"style"The minimum severity reported, as --severity: error, warning, info, style
excludestring[][]Codes to exclude, as --exclude (SC2034 or 2034)
includestring[][]Codes to report exclusively, as --include; empty: all codes
enablestring[][]Optional checks to enable, as --enable (all for every one)
rcfilestring""A .shellcheckrc to use instead of searching for one, as --rcfile
norcboolfalseIgnore .shellcheckrc files, as --norc
check_sourcedboolfalseReport findings in sourced files too, as --check-sourced
extended_analysisstring""Dataflow analysis, as --extended-analysis: true, false, or empty for the default (directives decide)
dep_autostring[][".shellcheckrc"]Config files that are inputs when they exist

Switching a repo from shellcheck: [processor.checker.shellcheck] → [processor.checker.ishellcheck]; a .shellcheckrc stays as it is, and options in args become the fields of the same name (args = ["-e", "SC1091", "-S", "warning"] becomes exclude = ["SC1091"], severity = "warning").

Batch support

Not used: checking in-process has no start-up cost for a batch to share, so files are checked in parallel as separate products.

Clean behavior

This processor is a Checker — rsconstruct clean outputs is a no-op for it (checkers produce no outputs). See Clean behavior and rsconstruct clean.