A check before running or in CI
With -S warning, info and style findings are neither printed nor counted:
the code is 1 only if warnings or errors remain.
shellcheck reads a bash or sh script without running it and finds bugs: unquoted variables, unchecked cd.
shellcheck backup.sh
check a script
shellcheck *.sh
several files at once
shellcheck -f gcc backup.sh
one line per finding
shellcheck -S warning backup.sh
only warnings and errors
shellcheck -e SC2034 backup.sh
hide this code
shellcheck -x backup.sh
follow files pulled in by source
shellcheck -s bash lib.sh
no shebang — treat it as bash
shellcheck -f diff backup.sh
ready-made fixes as a diff
shellcheck is a linter for bash and sh scripts. It reads the
script's text without running anything and points at the places where the
script will break or behave differently from what you meant. A finding is the
script line, a ^-- marker under the problem spot, an SCxxxx code, a level
and an explanation:
Often a Did you mean: line with a corrected line follows. For cp $f /tmp/
that is SC2086 (info): Double quote to prevent globbing and word splitting.
and the suggestion cp "$f" /tmp/. Every code has a page with a detailed
write-up: https://www.shellcheck.net/wiki/SC2086. SC1xxx codes are parsing errors,
SC2xxx are logic errors, SC3xxx are things POSIX sh does not have.
| Level | Meaning |
|---|---|
| error | the script will not work as written |
| warning | almost certainly a bug |
| info | works until it meets a space, an empty value or a \ |
| style | there is a simpler or sturdier way |
shellcheck picks the shell from the #! line: the file above starts with
#!/bin/sh, so [[ ]] is flagged — sh has no such thing. Its
exit code is 0 with no findings, 1 with findings, 2 when a file
could not be read.
| Code | What is wrong | How to fix it |
|---|---|---|
| SC2086 | an unquoted $var splits into words |
"$var" |
| SC2164 | an unchecked cd — the work continues in another directory |
cd /srv/app || exit |
| SC2045 | a loop over ls output |
for f in "$dir"/* |
| SC2162 | read without -r mangles \ |
read -r line |
| SC2181 | $? instead of testing the command itself |
if ! cmd; then |
| SC2034 | an unused variable — often a typo in its name | check the name |
| SC2115 | rm -rf "$dir/"* with an empty dir deletes /* |
"${dir:?}/"* |
| SC2155 | export x=$(cmd) hides the code of cmd |
x=$(cmd), then export x |
The main options are in the table below. The full list is in
man shellcheck.
| Option | What it does | Example |
|---|---|---|
-f --format tty|gcc|diff|json |
Output format; gcc gives one line per finding, diff gives ready fixes. | shellcheck -f gcc backup.sh |
-S --severity error|warning|info|style |
Show findings of this level and above. | shellcheck -S warning *.sh |
-e --exclude SC2086,SC2034 |
Hide findings with these codes. | shellcheck -e SC2034 backup.sh |
-s --shell bash|sh|dash|ksh |
Which shell to assume when the file has no | shellcheck -s bash lib.sh |
-x --external-sources |
Follow files pulled in with source or a dot. | shellcheck -x deploy.sh |
# shellcheck disable= SCxxxx |
A comment in the script — skip this check for the next command. | # shellcheck disable=SC2086 |
-o --enable CHECK|all |
Turn on optional checks; list them with shellcheck --list-optional. | shellcheck -o all backup.sh |
# shellcheck shell= bash |
On the first line of a file without | # shellcheck shell=bash |
Nothing found. Try a shorter query.
With -S warning, info and style findings are neither printed nor counted:
the code is 1 only if warnings or errors remain.
The comment covers the next command. Write the reason next to it: in a month it will not be obvious.
A .shellcheckrc file in the script's directory or above it:
Without -x, shellcheck prints SC1091 … Not following and does not see the
functions from the sourced file.
Only simple findings come with a fix: quotes, || exit. How to read a diff is
covered in diff-patch.
The file has no #! line, so shellcheck does not know which shell reads it.
Add #!/bin/bash, or, for a sourced file, put # shellcheck shell=bash on
the first line.
There is no file at that path: a typo in the name, or you are in the wrong
directory. shellcheck exits with code 2.
shellcheck does not know your intent: sometimes word splitting or a variable
from another file is deliberate. Silence the finding with
# shellcheck disable=SCxxxx above the line and write down why.
shellcheck checks only the text. It cannot know whether a file exists,
whether permissions suffice, what PATH a service has or what a command will
return. Those errors appear at run time: check exit codes and
the journal.
A suggestion fixes one finding and cannot see your intent. In the example
above for f in $(ls "$src") got its quotes but still loops over ls output —
SC2045 is still there. And quotes around a variable that is meant to split into
several options (rsync $RSYNC_OPTS) glue them into one argument and break the
command. For a set of options, use an array:
opts=(-a --delete); rsync "${opts[@]}" src/ dst/.
shellcheck teaches you to check.[ ] and [[ ]], where quoting matters most.ls output.-f diff output.Found an inaccuracy? Report an error in the article