Introduction
ShellCheck, by Vidar Holen, is the bash linter everyone should run. It catches the quiet killers: unquoted variables that break on filenames with spaces, rm -rf "" disasters from typo'd variables, subshell scoping bugs, portability issues between bash/sh/ash/dash, and dozens more categories of ambiguity that only show up on edge-case inputs.
With over 39,000 GitHub stars, ShellCheck is baked into VS Code, vim/Neovim, CI pipelines everywhere, and https://shellcheck.net. Running it on every script before committing is one of the highest-ROI habits a developer can adopt.
What ShellCheck Does
ShellCheck parses your script with a full shell AST, runs dozens of rule checks (SC1xxx/SC2xxx/SC3xxx codes), and prints warnings with explanations plus links to the wiki. Run it from CLI, an editor plugin, CI, or the web version. Output is also JSON, GCC-style, checkstyle — easy to integrate anywhere.
Architecture Overview
script.sh
|
[ShellCheck parser (Haskell)]
Full bash/POSIX AST
|
[Rule engine]
200+ rules organized by code
SC1xxx: parse errors
SC2xxx: common mistakes
SC3xxx: POSIX portability
|
[Output]
TTY (colored, default)
JSON, checkstyle, gcc, quiet, diff
|
[Editor / CI integrations]
VS Code, Neovim, Sublime, Atom,
GitHub Actions, GitLab CI,
pre-commit, reviewdogSelf-Hosting & Configuration
# .shellcheckrc at project root
external-sources=true # follow `source` directives
source-path=SCRIPTDIR # resolve relative to script
enable=all
disable=SC1091 # do not complain about unresolved sources
shell=bash # default dialect
# Inline directives
# shellcheck disable=SC2016
echo "Literal \$HOME here"
# shellcheck source=./lib/common.sh
source ./lib/common.sh# GitHub Actions: fail PR if shell scripts have issues
name: shellcheck
on: [pull_request]
jobs:
lint:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: ludeeus/action-shellcheck@master
with:
severity: warning
format: gccKey Features
- 200+ rules — parse errors, quoting, portability, subshell gotchas, security
- Multi-dialect — bash, ksh, dash, sh (POSIX), default configurable
- Explain + fix — each warning links to the wiki with examples and fixes
- Editor integrations — VS Code, vim, Neovim, Sublime, Atom, JetBrains
- CI-ready — GCC-style or JSON output, standard exit codes
- .shellcheckrc — project-level config for rules + source resolution
- Inline disables —
# shellcheck disable=SCxxxxfor specific lines - Offline + deterministic — no network, reproducible output
Comparison with Similar Tools
| Feature | ShellCheck | shfmt | bashate | bash -n | checkbashisms |
|---|---|---|---|---|---|
| Detects bugs | Yes (200+) | No (formatter) | Limited | Parse-only | POSIX-only |
| Formats | No (use shfmt) | Yes | No | No | No |
| Multi-dialect | Yes | Yes | bash-focused | Yes (via -n) | sh/dash focus |
| CI integrations | Many | Many | Limited | Universal | Limited |
| Best For | Catching bugs | Formatting | Openstack style | Syntax check | POSIX portability |
FAQ
Q: Do I run shellcheck on every script? A: Yes — make it a pre-commit hook and a CI check. The ROI is enormous; most shell-script production incidents are caused by something ShellCheck would have caught.
Q: How do I suppress a specific warning?
A: Use # shellcheck disable=SCxxxx above the offending line. Always add a comment explaining why; otherwise future you will be confused.
Q: ShellCheck vs shfmt? A: Complementary. ShellCheck finds bugs; shfmt formats code. Run both in CI — shfmt first (formatting can't fail), then ShellCheck.
Q: Can ShellCheck auto-fix issues?
A: Partially (with --format=diff and --extended-analysis). Most fixes require human judgment (is this variable ever empty? is this expansion safe?).
Sources
- GitHub: https://github.com/koalaman/shellcheck
- Website: https://www.shellcheck.net
- License: GPL-3.0