SHELLCHECK(1)

SHELLCHECK(1) πŸ’…βœ¨

okay bestie you clicked on the manpage for shellcheck and I'm about to make it make sense. this is part 1 so don't go anywhere, there's more coming 🫢


🌸 NAME 🌸

shellcheck β€” Shell script analysis tool πŸ’–


πŸ¦‹ SYNOPSIS πŸ¦‹

shellcheck [OPTIONS...]  FILES...

like, you give it some files, and optionally some options. that's the whole shape of it, periodt.


πŸ’– DESCRIPTION πŸ’–

ShellCheck is a static analysis and linting tool for sh/bash scripts. no cap. it's mostly built to catch the classic beginner-to-intermediate level syntax oopsies and pitfalls β€” the ones where your shell just gives you some cryptic lil error or does something genuinely unhinged instead of telling you what's wrong 😭 β€” but it also reports on a handful of more advanced issues where the corner cases can cause delayed failures. those are the sneaky ones and honestly they're so annoying.

The way ShellCheck works is that it gives shell specific advice. so look at this line:

(( area = 3.14*r*r ))

and now watch how the answer changes based on who's running it, it's giving "it depends" and it's right:

  • For scripts starting with #!/bin/sh (or when using -s sh), ShellCheck will warn that (( .. )) is not POSIX compliant (similar to checkbashisms).
  • For scripts starting with #!/bin/bash (or using -s bash), ShellCheck will warn that decimals are not supported.
  • For scripts starting with #!/bin/ksh (or using -s ksh), ShellCheck will not warn at all, as ksh supports decimals in arithmetic contexts.

see?? same line, three totally different verdicts. context is everything bestie ✨


✨ OPTIONS ✨

okay here's the whole menu, every single one, no skips because we don't do that here πŸ’…

  • -a, --check-sourced β€” Emit warnings in sourced files. Normally shellcheck will only warn about issues in the specified files. With this option, any issues in sourced files will also be reported. so the drama doesn't stop at the front door anymore πŸšͺ
  • -C[WHEN], --color[=WHEN] β€” For TTY output, enable colors always, never or auto. The default is auto. --color without an argument is equivalent to --color=always.
  • -i CODE1[,CODE2...], --include=CODE1[,CODE2...] β€” Explicitly include only the specified codes in the report. Subsequent -i options are cumulative, but all the codes can be specified at once, comma-separated as a single argument. Include options override any provided exclude options. so -i wins the argument, iconic behavior honestly πŸ‘‘
  • -e CODE1[,CODE2...], --exclude=CODE1[,CODE2...] β€” Explicitly exclude the specified codes from the report. Subsequent -e options are cumulative, but all the codes can be specified at once, comma-separated as a single argument.
  • --extended-analysis=true/false β€” Enable/disable Dataflow Analysis to identify more issues (default true). If ShellCheck uses too much CPU/RAM when checking scripts with several thousand lines of code, extended analysis can be disabled with this flag or a directive. This flag overrides directives and rc files. so it's the boss of the other settings, respect it.
  • -f FORMAT, --format=FORMAT β€” Specify the output format of shellcheck, which prints its results in the standard output. Subsequent -f options are ignored β€” first one wins β€” see FORMATS below for more information.
  • --list-optional β€” Output a list of known optional checks. These can be enabled with -o flags or enable directives. window shopping πŸ›οΈ
  • --norc β€” Don't try to look for .shellcheckrc configuration files. going no-contact with your rc files, valid.
  • --rcfile RCFILE β€” Prefer the specified configuration file over searching for one in the default locations. picking your favorite child, we love to see it.
  • -o NAME1[,NAME2...], --enable=NAME1[,NAME2...] β€” Enable optional checks. The special name all enables all of them. Subsequent -o options accumulate. This is equivalent to specifying enable directives.
  • -P SOURCEPATH, --source-path=SOURCEPATH β€” Specify paths to search for sourced files, separated by : on Unix and ; on Windows. This is equivalent to specifying search-path directives.
  • -s shell, --shell=shell β€” Specify Bourne shell dialect. Valid values are sh, bash, dash, ksh, and busybox. The default is to deduce the shell from the file's shell directive, shebang, or .bash/.bats/.dash/.ksh extension, in that order. sh refers to POSIX sh (not the system's), and will warn of portability issues.
  • -S SEVERITY, --severity=severity β€” Specify minimum severity of errors to consider. Valid values in order of severity are error, warning, info and style. The default is style.
  • -V, --version β€” Print version information and exit. short and sweet, love a diva who knows when to leave πŸ’…
  • -W NUM, --wiki-link-count=NUM β€” For TTY output, show NUM wiki links to more information about mentioned warnings. Set to 0 to disable them entirely.
  • -x, --external-sources β€” Follow source statements even when the file is not specified as input. By default, shellcheck will only follow files specified on the command line (plus /dev/null). This option allows following any file the script may source.

This option may also be enabled using external-sources=true in .shellcheckrc. This flag takes precedence.

  • FILES... β€” One or more script files to check, or "-" for standard input. yeah you can pipe it in, she's flexible like that πŸŽ€

🌈 FORMATS 🌈

pick your flavor bestie, there's a -f for every vibe

tty

Plain text, human readable output. This is the default.

gcc

GCC compatible output. Useful for editors that support compiling and showing syntax errors.

For example, in Vim, :set makeprg=shellcheck\ -f\ gcc\ % will allow using :make to check the script, and :cnext to jump to the next error.

<file>:<line>:<column>: <type>: <message>

checkstyle

Checkstyle compatible XML output. Supported directly or through plugins by many IDEs and build monitoring systems.

<?xml version='1.0' encoding='UTF-8'?>
<checkstyle version='4.3'>
  <file name='file'>
    <error
      line='line'
      column='column'
      severity='severity'
      message='message'
      source='ShellCheck.SC####' />
    ...
  </file>
  ...
</checkstyle>

diff

Auto-fixes in unified diff format. Can be piped to git apply or patch -p1 to automatically apply fixes. autofix era πŸ”₯

--- a/test.sh
+++ b/test.sh
@@ -2,6 +2,6 @@
 ## Example of a broken script.
 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

json1

Json is a popular serialization format that is more suitable for web applications. ShellCheck's json is compact and contains only the bare minimum. Tabs are counted as 1 character.

{
  comments: [
    {
      "file": "filename",
      "line": lineNumber,
      "column": columnNumber,
      "level": "severitylevel",
      "code": errorCode,
      "message": "warning message"
    },
    ...
  ]
}

json

This is a legacy version of the json1 format. It's a raw array of comments, and all offsets have a tab stop of 8. it's giving "we keep it around for the old heads", respect 🫢

quiet

Suppress all normal output. Exit with zero if no issues are found, otherwise exit with one. Stops processing after the first issue. she's blunt, she's efficient, she ate.


πŸŽ€ DIRECTIVES πŸŽ€

ShellCheck directives can be specified as comments in the shell script. If they appear before the first command, they are considered file-wide. Otherwise, they apply to the immediately following command or block:

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

For example, to suppress SC2035 about using ./*.jpg:

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

To tell ShellCheck where to look for an otherwise dynamically determined file:

# shellcheck source=./lib.sh
source "$(find_install_dir)/lib.sh"

Here a shell brace group is used to suppress a warning on multiple lines:

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

Valid keys are:

  • disable β€” Disables a comma separated list of error codes for the following command. The command can be a simple command like echo foo, or a compound command like a function definition, subshell block or loop. A range can be be specified with a dash, e.g. disable=SC3000-SC4000 to exclude 3xxx. All warnings can be disabled with disable=all.
  • enable β€” Enable an optional check by name, as listed with --list-optional. Only file-wide enable directives are considered.
  • extended-analysis β€” Set to true/false to enable/disable dataflow analysis. Specifying # shellcheck extended-analysis=false in particularly large (2000+ line) auto-generated scripts will reduce ShellCheck's resource usage at the expense of certain checks. Extended analysis is enabled by default.
  • external-sources β€” Set to true in .shellcheckrc to always allow ShellCheck to open arbitrary files from 'source' statements (the way most tools do).

This option defaults to false only due to ShellCheck's origin as a remote service for checking untrusted scripts. It can safely be enabled for normal development.

  • source β€” Overrides the filename included by a source/. statement. This can be used to tell shellcheck where to look for a file whose name is determined at runtime, or to skip a source by telling it to use /dev/null.
  • source-path β€” Add a directory to the search path for source/. statements (by default, only ShellCheck's working directory is included). Absolute paths will also be rooted in these paths. The special path SCRIPTDIR can be used to specify the currently checked script's directory, as in source-path=SCRIPTDIR or source-path=SCRIPTDIR/../libs. Multiple paths accumulate, and -P takes precedence over them.
  • shell β€” Overrides the shell detected from the shebang. This is useful for files meant to be included (and thus lacking a shebang), or possibly as a more targeted alternative to 'disable=SC2039'.

okay that's the whole first half, don't close the tab yet because there is MORE πŸ’–

πŸ“ RC FILES πŸ“

Unless you pass --norc, bestie, ShellCheck goes on a lil treasure hunt πŸ•΅οΈβ™€οΈ β€” it looks for a file called .shellcheckrc or shellcheckrc in the script's own directory, and then in every parent directory going up. If it finds one, it reads the key=value pairs inside and treats them as file-wide directives. Slay.

Here's a whole example .shellcheckrc so you can see the vibe:

# 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

# Since 0.9.0, values can be quoted with '' or "" to allow spaces
source-path="My Documents/scripts"

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

# Turn on warnings for unquoted variables with safe values
enable=quote-safe-variables

# Turn on warnings for unassigned uppercase variables
enable=check-unassigned-uppercase

# Allow [ ! -z foo ] instead of suggesting -n
disable=SC2236

So if there's no .shellcheckrc anywhere in the parent directories, ShellCheck keeps looking β€” first in ~/.shellcheckrc, then in $XDG_CONFIG_HOME (which is usually ~/.config/shellcheckrc) on Unix, or %APPDATA%/shellcheckrc on Windows. And heads up: only the first file it finds gets used. One and done, periodt. πŸ’…

Snap girlies listen up 🫢 β€” the Snap sandbox won't let ShellCheck touch hidden files. So use shellcheckrc without the dot. The dot is the problem, not you.
Docker besties πŸ“¦</> β€” ShellCheck can only peek at files that are actually mounted into the container, so ~/.shellcheckrc is just not gonna be read. Sad but true.

🌱 ENVIRONMENT VARIABLES 🌱

You can set the environment variable SHELLCHECK_OPTS with your default flags, like so:

export SHELLCHECK_OPTS='--shell=bash --exclude=SC2016'

Its value gets split on spaces and prepended to the command line every single time you invoke ShellCheck. Every. Single. Time. πŸ’–


🚦 RETURN VALUES 🚦

ShellCheck speaks in exit codes, and here's the whole translation guide:

  • 0 β€” every file scanned successfully, zero issues. Flawless. No notes. πŸ‘‘
  • 1 β€” every file scanned successfully, but some issues showed up. (Still scanned them all though! That's the win.)
  • 2 β€” some files just could not be processed (like, say, file not found). 🫠
  • 3 β€” ShellCheck was invoked with bad syntax (cough, unknown flag, cough). πŸ™ˆ
  • 4 β€” ShellCheck was invoked with bad options (like an unknown formatter, bestie, what did you type).

🌍 LOCALE 🌍

This version of ShellCheck is only available in English, sorry to all my multilingual icons. 😭

All files get leniently decoded as UTF-8, and if there are invalid sequences it falls back to ISO-8859-1. LC_CTYPE is respected for output, and it defaults to UTF-8 for locales where the encoding isn't specified (like the C locale).

Windows girlies πŸ’€ β€” if you're seeing commitBuffer: invalid argument (invalid character), set your terminal to use UTF-8 with chcp 65001. That's the fix, you're welcome. πŸŽ€

🧩 KNOWN INCOMPATIBILITIES 🧩

(If literally nothing in this section makes sense to you, you are so unlikely to be affected by any of it. Carry on, babe.)

To avoid confusing and misguided suggestions, ShellCheck requires function bodies to be either { brace groups; } or ( subshells ) β€” no in-between nonsense. And function names containing []*=! are only recognized after a function keyword.

So these two unconventional function definitions? Identical in Bash, but ShellCheck only recognizes the second one:

[x!=y] () [[ $1 ]]
function [x!=y] () { [[ $1 ]]; }

Shells without the function keyword don't even allow those characters in function names in the first place. And function names containing {}? Not supported at all. Nope. Not happening. πŸ’€

Also, if ShellCheck sees [x!=y] it will assume you meant an invalid comparison. So if you wanna actually invoke that function above, quote the command like '[x!=y]', or β€” if you wanna keep the same globbing behavior β€” use command [x!=y].

ShellCheck also puts extra restrictions on the [ command to help catch common invalid uses. Like, POSIX says [ $x= 1 ] is defined, but ShellCheck will assume you actually meant the way more likely comparison [ "$x" = 1 ] and fail accordingly. For unconventional or dynamic uses of the [ command, use test or \[ instead. 🫑


πŸ› REPORTING BUGS πŸ›

Found a bug? Got an issue? Take it to GitHub, bestie:

https://github.com/koalaman/shellcheck/issues


πŸ‘‘ AUTHORS πŸ‘‘

ShellCheck is developed and maintained by Vidar Holen, with assistance from a long list of wonderful contributors. Absolute icons, every one of them. 🫢


Copyright 2012-2025, Vidar Holen and contributors. Licensed under the GNU General Public License version 3 or later, see https://gnu.org/licenses/gpl.html


πŸ”— SEE ALSO πŸ”—

sh(1), bash(1), dash(1), ksh(1)


and that's the whole page, bestie!! ✨ This one's the Shell script analysis tool β€” that's the source name right there in the footer β€” and it lives at SHELLCHECK(1) in the manuals. Section 1, the user commands, where it belongs. πŸ¦‹ Go forth and lint, you absolute main character. πŸ’–πŸŒΈ

πŸ“¦ where this page comes from