Jesse A. Green / notebook

ShellCheck User 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.

ShellCheck is a static analysis tool for sh/bash shell scripts. You point it at a script, and it reports warnings and suggestions for syntax errors, semantic problems, and subtle pitfalls — the kind of bugs where the shell either gives a cryptic error message or silently does the wrong thing.

This guide shows you how to install ShellCheck, run it from the terminal, wire it into your editor and CI pipeline, control which warnings you see, and silence false positives. Everything here is drawn directly from ShellCheck's own source and manual at version 0.11.0.

Note

ShellCheck gives shell-specific advice. The same line can be valid in ksh, a portability warning in sh, and a "decimals not supported" warning in bash. ShellCheck figures out which shell you mean from the shebang, an extension, a directive, or the --shell flag.


1. Quick Start

Check a script in one command:

shellcheck myscript.sh

ShellCheck prints each issue with the offending line, a caret pointing at the problem, a severity, a unique code (like SC2086), and a short explanation:

In myscript.sh line 3:
echo $name
     ^---^ SC2086 (info): Double quote to prevent globbing and word splitting.

Did you mean:
echo "$name"

If ShellCheck finds no issues, it prints nothing and exits with status 0. If it finds issues, it exits with status 1. That makes it a drop-in gate for build and test pipelines.

Tip

Try ShellCheck instantly without installing anything: paste a script into https://www.shellcheck.net. The web version always tracks the latest git build.


2. Installation

ShellCheck ships as a single self-contained binary. Install it with your platform's package manager, download a pre-compiled release, or build it from source.

Debian / Ubuntu

sudo apt install shellcheck

Fedora

dnf install ShellCheck

Arch

pacman -S shellcheck

macOS (Homebrew)

brew install shellcheck

macOS (MacPorts)

sudo port install shellcheck

Windows

choco install shellcheck
winget install --id koalaman.shellcheck
scoop install shellcheck

Other package managers that carry ShellCheck:

Platform Command
EPEL / RHEL sudo yum -y install epel-release && sudo yum install ShellCheck
Gentoo emerge --ask shellcheck
FreeBSD pkg install hs-ShellCheck
OpenBSD pkg_add shellcheck
openSUSE zypper in ShellCheck
Solus eopkg install shellcheck
conda-forge conda install -c conda-forge shellcheck
Snap snap install --channel=edge shellcheck
Nix nix-env -iA nixpkgs.shellcheck
Flox flox install shellcheck
Cabal cabal update && cabal install ShellCheck
Stack stack update && stack install ShellCheck

2.1 Pre-compiled binaries

Download a release archive directly when no package is available. Binaries come as tar.xz files (Windows ships a .zip), so make sure xz is installed (apt install xz-utils on Debian/Ubuntu, yum -y install xz on RHEL/Fedora).

A minimal installer for Linux x86_64:

scversion="stable"   # or "v0.4.7" for a specific version, or "latest" for daily git builds
wget -qO- "https://github.com/koalaman/shellcheck/releases/download/${scversion?}/shellcheck-${scversion?}.linux.x86_64.tar.xz" | tar -xJv
cp "shellcheck-${scversion}/shellcheck" /usr/bin/
shellcheck --version

Pre-compiled targets published for each release: Linux x86_64, armv6hf (Raspberry Pi) and aarch64 (ARM64) — all statically linked — plus macOS aarch64/x86_64 and Windows x86.

Important

Pin a specific ShellCheck version in CI rather than always pulling stable. New releases add new warnings, and an unpinned upgrade can suddenly fail a build that passed yesterday.

2.2 Docker

Run ShellCheck without installing anything on the host:

docker run --rm -v "$PWD:/mnt" koalaman/shellcheck:stable myscript

Use the tag :stable for the latest release, :latest for daily builds, or a version tag like :v0.4.7. If you want a base image to extend, use koalaman/shellcheck-alpine, an Alpine image with ShellCheck preinstalled.

Warning

Inside a container, ShellCheck can only read files mounted into it. A ~/.shellcheckrc on the host will not be read. Mount your config explicitly or commit a project-level .shellcheckrc.

2.3 Verify the install

shellcheck --version
ShellCheck - shell script analysis tool
version: 0.11.0
license: GNU General Public License, version 3
website: https://www.shellcheck.net

3. Everyday Usage

3.1 Check one or more files

Pass any number of script paths. ShellCheck checks each one and exits non-zero if any file has issues:

shellcheck deploy.sh backup.sh cron-rotate.sh

3.2 Read from standard input

Use - as the filename to check piped input:

cat myscript.sh | shellcheck -

Note

When reading from stdin, ShellCheck can't deduce the shell from a file extension. Add a shebang to the script, or use --shell to set the dialect explicitly (see §4.1).

3.3 Check scripts in a directory

ShellCheck has no built-in recursive directory walk — let your shell or find supply the file list:

# All .sh files in a folder
shellcheck scripts/*.sh

# Recursively, including scripts without a .sh extension (matched by shebang via file)
find . -type f -name '*.sh' -exec shellcheck {} +

3.4 Show fewer or more warnings by severity

Every issue has one of four severities. From most to least severe: error, warning, info, style. By default ShellCheck shows all of them (minimum severity style). Raise the floor with --severity (-S) to focus on the important stuff:

# Only show errors and warnings; hide info and style suggestions
shellcheck --severity=warning deploy.sh
Severity Meaning
error Will almost certainly break the script
warning Likely a bug or dangerous behavior
info A correctness or clarity improvement
style A stylistic suggestion

4. Controlling What Gets Checked

4.1 Pick the shell dialect (--shell)

Force ShellCheck to analyze a script as a specific shell, overriding the shebang. Valid dialects: sh, bash, dash, ksh, busybox.

shellcheck --shell=dash install.sh

ShellCheck decides which shell to use in this order:

  1. The --shell / -s flag
  2. A # shellcheck shell=... directive in the file
  3. The shebang line (#!/bin/bash, #!/bin/sh, …)
  4. The file extension: .ksh → ksh, .bash/.bats/.envrc → bash, .dash → dash
  5. Fallback to bash (and emit SC2148 if there's no shebang at all)

Note

sh means POSIX sh, not whatever /bin/sh happens to be on your system. With --shell=sh, ShellCheck warns about non-portable "bashisms," much like checkbashisms.

4.2 Exclude specific warnings (--exclude / -e)

Silence warnings by code across the whole run. Codes are comma-separated, and the SC prefix is optional:

# Ignore SC2086 (unquoted variables) and SC2034 (unused variables)
shellcheck --exclude=SC2086,SC2034 myscript.sh

4.3 Include only specific warnings (--include / -i)

The inverse — report only the listed codes and nothing else. Include overrides exclude:

# Report only SC2086, regardless of anything else in the script
shellcheck --include=SC2086 myscript.sh

4.4 Enable optional checks (--enable / -o)

ShellCheck ships a set of stricter, opt-in checks that are off by default. Turn them on by name, comma-separated, or all at once with all:

shellcheck --enable=require-variable-braces,quote-safe-variables myscript.sh
shellcheck --enable=all myscript.sh

List every optional check with --list-optional. The full set in this version:

Name What it flags
quote-safe-variables Suggests quoting variables even when their values have no metacharacters
avoid-nullary-conditions Suggests explicitly using -n in [ $var ]
avoid-negated-conditions Suggests removing unnecessary comparison negations ([ ! "$var" -eq 1 ] → -ne)
add-default-case Suggests adding a default *) case to case statements
require-variable-braces Suggests braces around all variable references ($var → ${var})
check-unassigned-uppercase Warns when uppercase variables are used but never assigned
require-double-brackets Requires [[ ]] and warns about [ ] in Bash/Ksh
check-set-e-suppressed Notifies when set -e is suppressed during a function invocation
check-extra-masked-returns Flags additional cases where a command's exit code is masked
useless-use-of-cat Flags Useless Use Of Cat (e.g. cat foo | grep bar)
deprecate-which Suggests command -v instead of which

Tip

Optional checks shine in established codebases that want to enforce a house style. Enable require-variable-braces and require-double-brackets to push a team toward consistent, modern Bash.

4.5 Tune dataflow analysis (--extended-analysis)

ShellCheck performs dataflow analysis to catch deeper issues (like code that can never run after an exit). It's on by default. For very large auto-generated scripts (thousands of lines) where CPU or RAM matter, turn it off:

shellcheck --extended-analysis=false bigscript.sh

Important

The --extended-analysis flag overrides both .shellcheckrc settings and in-file directives. It's the final word on whether dataflow analysis runs.

4.6 Check sourced files

By default, ShellCheck only reports issues in the files you name on the command line. Two flags change that:

  • --check-sourced (-a) — also report issues found inside files pulled in via source/..
  • --external-sources (-x) — actually follow source statements that point to files you didn't list as input (by default only /dev/null and named inputs are followed).
shellcheck --external-sources --check-sourced main.sh

When the path to a sourced file is computed at runtime, tell ShellCheck where to look with --source-path (-P), or with a source/source-path directive (see §6):

# Search these directories for sourced files (':' separator on Unix, ';' on Windows)
shellcheck --source-path=lib:vendor --external-sources main.sh

The special token SCRIPTDIR in a source path resolves to the directory of the script being checked.


5. Output Formats

Choose how ShellCheck prints results with --format (-f). This is what lets editors and CI systems consume its output.

shellcheck --format=gcc myscript.sh
Format Best for
tty Human-readable colored text (the default)
gcc Editor integration — <file>:<line>:<col>: <type>: <message>, like a compiler
checkstyle Checkstyle-compatible XML for IDEs and build dashboards
diff Auto-fixes as a unified diff you can pipe to git apply or patch -p1
json1 Compact JSON for web apps and custom tooling (tabs count as 1 column)
json Legacy JSON: a raw array of comments, tab stop of 8
quiet No output at all; exits 0 if clean, 1 otherwise, stopping at the first issue

5.1 Apply auto-fixes

The diff format emits a patch that fixes issues ShellCheck knows how to fix. Pipe it straight into your VCS:

shellcheck --format=diff myscript.sh | git apply
--- a/myscript.sh
+++ b/myscript.sh
@@ -2,6 +2,6 @@
 for f in $(ls *.m3u)
 do
-  grep -qi hq.*mp3 $f \
+  grep -qi hq.*mp3 "$f" \
     && echo -e 'Playlist $f contains a HQ file in mp3 format'
 done

Caution

Review the diff before applying it to important scripts. Auto-fixes are mechanical and occasionally change behavior in ways you didn't intend — especially around quoting and word splitting. Commit your work first.

For tty output, two flags fine-tune the display:

shellcheck --color=always myscript.sh    # auto (default), always, or never
shellcheck --wiki-link-count=0 myscript.sh  # hide the "For more info" wiki links

6. Inline Directives: Controlling ShellCheck From Your Script

The most precise way to manage warnings is with # shellcheck comment directives placed in the script itself. A directive before the first command applies to the whole file; otherwise it applies to the immediately following command or block.

# shellcheck key=value key=value
command-or-structure

For example, to silence SC2035 on one line:

# shellcheck disable=SC2035
echo "Files: " *.jpg

To cover several lines, attach the directive to a brace group:

# shellcheck disable=SC2016
{
  echo 'Modifying $PATH'
  echo 'PATH=foo:$PATH' >> ~/.bashrc
}

6.1 Valid directive keys

Key Effect
disable Disable a comma-separated list of codes for the following command. Supports ranges (disable=SC3000-SC4000) and disable=all
enable Enable an optional check by name (file-wide only)
extended-analysis true/false to toggle dataflow analysis (useful per-file for huge generated scripts)
external-sources Set true in .shellcheckrc to allow opening any sourced file
source Override the filename a source/. statement reads — point it at the real file, or at /dev/null to skip it
source-path Add a directory to the search path for source/.; the SCRIPTDIR token means the checked script's directory
shell Override the shell detected from the shebang (handy for sourced files that have no shebang)
# Tell ShellCheck where a dynamically-located library really lives
# shellcheck source=./lib.sh
source "$(find_install_dir)/lib.sh"

Note

A disable range is inclusive, so # shellcheck disable=SC3000-SC4000 silences every SC3xxx portability warning. Use disable=all to turn off ShellCheck entirely for the following command.


7. Project Configuration: .shellcheckrc

Instead of repeating flags on every invocation, drop a .shellcheckrc file in your project. Unless you pass --norc, ShellCheck looks for .shellcheckrc (or shellcheckrc without the dot) in the script's directory and every parent directory, using the first one it finds. It reads key=value pairs and treats them as file-wide directives.

# Look for 'source'd files relative to the checked script,
# and also look for absolute paths in /mnt/chroot
source-path=SCRIPTDIR
source-path=/mnt/chroot

# Values can be quoted with '' or "" to allow spaces (since 0.9.0)
source-path="My Documents/scripts"

# Allow opening any 'source'd file, even if not specified as input
external-sources=true

# Turn on optional checks
enable=quote-safe-variables
enable=check-unassigned-uppercase

# Permanently silence a code project-wide
disable=SC2236

If no project-level file is found, ShellCheck falls back to:

  • Unix: ~/.shellcheckrc, then $XDG_CONFIG_HOME/shellcheckrc (usually ~/.config/shellcheckrc)
  • Windows: %APPDATA%/shellcheckrc

Override the search entirely with --rcfile path/to/config, or disable it with --norc.

Warning

Snap users: the Snap sandbox blocks access to hidden files. Name the file shellcheckrc (no leading dot) instead of .shellcheckrc.


8. Setting Defaults With an Environment Variable

The SHELLCHECK_OPTS environment variable holds default flags that are split on spaces and prepended to every invocation:

export SHELLCHECK_OPTS='--shell=bash --exclude=SC2016'
shellcheck myscript.sh    # behaves as: shellcheck --shell=bash --exclude=SC2016 myscript.sh

This is handy for setting a personal preference (like always excluding a code you disagree with) without editing project files.


9. Integrating ShellCheck

9.1 In your editor

ShellCheck has plugins for most editors, usually surfacing warnings inline as you type:

Editor Plugin(s)
Vim ALE, Neomake, Syntastic
Emacs Flycheck, Flymake
Sublime Text SublimeLinter-shellcheck
Pulsar (Atom) linter-shellcheck-pulsar
VS Code vscode-shellcheck
Most others Via the gcc output format

For an editor without a dedicated plugin, the gcc format usually works. In Vim:

:set makeprg=shellcheck\ -f\ gcc\ %

Then :make checks the script and :cnext jumps between errors.

9.2 In builds and test suites

ShellCheck makes canonical use of exit codes, so it slots straight into any pipeline. In a Makefile:

check-scripts:
	# Fail if any of these files have warnings
	shellcheck myscripts/*.sh

In a CI config:

script:
  # Fail the build if any script has warnings
  - shellcheck myscripts/*.sh

Many platforms ship ShellCheck pre-installed or as a first-class step, including Travis CI, Codacy, Code Climate, Code Factor, CircleCI (via the ShellCheck Orb), GitHub Actions (Linux), and Trunk Code Quality.

9.3 As a pre-commit hook

With pre-commit, add the hook to .pre-commit-config.yaml:

repos:
-   repo: https://github.com/koalaman/shellcheck-precommit
    rev: v0.11.0
    hooks:
    -   id: shellcheck
#       args: ["--severity=warning"]  # Optionally only show errors and warnings

10. What ShellCheck Catches

ShellCheck flags hundreds of distinct issues, each with a unique SC#### code. Rather than reproduce the full catalog (it lives, with long-form explanations, on the project wiki), here is a representative sample of the categories it covers.

Quoting

echo $1                           # Unquoted variables (globbing/word-splitting)
find . -name *.ogg                # Unquoted glob in find/grep patterns
echo 'Path is $PATH'              # Variables that won't expand in single quotes
touch $@                          # Unquoted $@

Conditionals

[[ $foo==0 ]]                     # Always true due to missing spaces
[ $1 -eq "shellcheck" ]           # Numeric comparison of strings
[ grep -q foo file ]              # Command without $(..)
(( 1 -lt 2 ))                     # Using test operators inside ((..))

Frequently misused commands

grep '*foo*' file                 # Globs where a regex is expected
sudo echo 'x' > /etc/profile      # Redirect happens outside sudo
while read h; do ssh "$h" uptime  # ssh eats the while-loop's stdin

Beginner syntax errors

var = 42                          # Spaces around = in assignment
$foo=42                           # $ on the left of an assignment
echo $var[14]                     # Missing {} in array reference
else if othercondition; then ..   # Using 'else if' instead of 'elif'

Robustness & portability

rm -rf "$STEAMROOT/"*             # Catastrophic rm if the variable is empty
for f in $(ls *.txt); do          # Iterating over ls output
local var=value                   # 'local' is undefined in POSIX sh
echo {1..10}                      # Brace ranges don't work in dash/sh

To read the rationale and fix for any specific code, visit https://github.com/koalaman/shellcheck/wiki/SC<NNNN> — for example, SC2086.

Tip

ShellCheck deliberately does not enforce indentation or formatting. For that, pair it with a formatter like shfmt.


11. Understanding Exit Codes

ShellCheck communicates results through its exit status — essential for scripting and CI gates:

Exit code Meaning
0 All files scanned, no issues found
1 All files scanned, some issues found
2 Some files could not be processed (e.g. file not found)
3 Invoked with bad syntax (e.g. an unknown flag)
4 Invoked with bad options (e.g. an unknown formatter)
if shellcheck --severity=error deploy.sh; then
  echo "No errors — safe to ship"
else
  echo "ShellCheck found problems (exit $?)"
fi

12. Troubleshooting

Error: SC2148 (error): Tips depend on target shell and yours is unknown

Cause: ShellCheck couldn't determine the shell. The file has no shebang, no recognized extension (.sh is too generic to pick a dialect), and no shell directive.

Solution: Add a shebang (#!/bin/bash), pass --shell=bash, or add # shellcheck shell=bash near the top of the file.

Prevention: Always start scripts with a shebang; for sourced library files that have none, add a # shellcheck shell=... directive.


Error: <file> was not specified as input (see shellcheck -x)

Cause: The script sources another file that you didn't list on the command line, and external sourcing is disabled (the default).

Solution: Add --external-sources (-x) to let ShellCheck follow the file, or set external-sources=true in .shellcheckrc.

Prevention: Keep an external-sources=true line in a project .shellcheckrc once you trust your own libraries.


Error: SC1090 (warning): ShellCheck can't follow non-constant source

Cause: A source/. target is built from a variable or command substitution, so ShellCheck can't statically resolve the path.

Solution: Add a directive telling ShellCheck where to look: # shellcheck source=./lib.sh above the source line, or # shellcheck source=/dev/null to skip it.

Prevention: Use source-path directives or the --source-path flag for libraries located at runtime.


Error: SC1091 (info): Not following: <file> was not specified as input

Cause: ShellCheck found the sourced file's path but isn't configured to read it.

Solution: Pass --external-sources, or add a source directive pointing at the real file.

Prevention: Combine --external-sources with --check-sourced to analyze libraries inline.


Problem: A warning is a false positive on one specific line

Cause: ShellCheck flagged code that is correct in your particular context.

Solution: Add # shellcheck disable=SC<NNNN> on the line immediately above the command. To silence a whole block, attach the directive to a { ...; } brace group.

Prevention: For codes you consistently disagree with project-wide, add disable=SC<NNNN> to .shellcheckrc.


Problem: openBinaryFile: permission denied

Cause: ShellCheck tried to read a file (often via a glob that matched a directory or an unreadable file) it doesn't have permission to open.

Solution: Pass only readable script files; refine your glob (e.g. *.sh rather than *) so it doesn't match directories.

Prevention: Be specific with the file list you hand to ShellCheck.


Problem: Garbled output on Windows (commitBuffer: invalid argument)

Cause: The terminal codepage isn't UTF-8.

Solution: Set the codepage with chcp 65001. In PowerShell ISE also run [Console]::OutputEncoding = [System.Text.Encoding]::UTF8. Use a TrueType font, not a Raster font.

Prevention: Configure your terminal to UTF-8 once for all shell work.


Problem: ShellCheck uses too much CPU/RAM on a huge script

Cause: Extended dataflow analysis is expensive on auto-generated scripts with thousands of lines.

Solution: Disable it with --extended-analysis=false, or add # shellcheck extended-analysis=false to that file.

Prevention: Disable extended analysis for known-large generated files via a directive.


13. Frequently Asked Questions

Q: Does ShellCheck modify my scripts? No. ShellCheck only reads and reports. The one way to get changes is to explicitly run --format=diff and apply the resulting patch yourself.

Q: How do I check a script that isn't named *.sh? Just pass the path — ShellCheck reads the shebang to pick the dialect. For files without an extension or shebang, use --shell or a shell directive.

Q: Can I silence one warning everywhere without editing every file? Yes. Use --exclude=SC<NNNN>, a disable=SC<NNNN> line in .shellcheckrc, or set SHELLCHECK_OPTS='--exclude=SC<NNNN>'.

Q: What's the difference between --exclude and --severity? --severity hides whole tiers of warnings by importance; --exclude removes individual codes regardless of severity. Use them together.

Q: How do I see every issue, including style suggestions? That's the default. ShellCheck shows everything down to style severity unless you raise the floor with --severity.

Q: What does the SC prefix mean, and is it required when excluding codes? SC stands for ShellCheck. It's optional when you pass codes to --exclude, --include, or a directive — 2086 and SC2086 are equivalent.

Q: How do I get more detail on a specific warning? Visit the wiki page for the code, e.g. https://github.com/koalaman/shellcheck/wiki/SC2086, or look at the wiki links shown in tty output (control how many with --wiki-link-count).

Q: Can ShellCheck check files my script sources? Yes — add --check-sourced to report inside them and --external-sources to follow files not listed as input.

Q: Does it work on zsh or fish? No. ShellCheck targets the Bourne-shell family: sh, bash, dash, ksh, and busybox. It doesn't analyze zsh or fish.

Q: How do I enforce stricter style, like always using braces? Turn on optional checks: --enable=require-variable-braces,require-double-brackets, or list them under enable= in .shellcheckrc. See --list-optional for the full set.

Q: How do I make my CI fail only on errors, not style nits? Run shellcheck --severity=error (or --severity=warning) so only the chosen tier and above affect the exit code.


14. Command Reference

Flag Long form Argument Description
-a --check-sourced — Also report issues in sourced files
-C --color [WHEN] Color in tty output: auto (default), always, never
-i --include CODE1,… Report only the listed codes (overrides --exclude)
-e --exclude CODE1,… Don't report the listed codes
--extended-analysis true/false Toggle dataflow analysis (default true; overrides directives/rc)
-f --format FORMAT Output format: tty, gcc, checkstyle, diff, json, json1, quiet
--list-optional — List all optional checks and exit
--norc — Don't read any .shellcheckrc
--rcfile RCFILE Use the specified config file instead of searching
-o --enable NAME1,… Enable optional checks (all for every one)
-P --source-path SOURCEPATH Directories to search for sourced files (: Unix / ; Windows)
-s --shell SHELL Dialect: sh, bash, dash, ksh, busybox
-S --severity SEVERITY Minimum severity: error, warning, info, style
-V --version — Print version information and exit
-W --wiki-link-count NUM Number of wiki links to show in tty output (0 disables)
-x --external-sources — Follow source statements to files not listed as input
--help — Show usage summary and exit

Configuration surfaces, in precedence order (later wins, except where noted):

  1. SHELLCHECK_OPTS environment variable (prepended to every invocation)
  2. .shellcheckrc / fallback user config
  3. In-file # shellcheck directives
  4. Command-line flags

Note

--extended-analysis and --external-sources from the command line take precedence over directives and rc files. A file directive can disable external sources but cannot enable them if the rc/flag didn't already allow it (you'll get SC1144).