Where to start First steps track · 15 stages
Linux Labs

shellcheck — checking scripts before they run

7 min Ubuntu 24.04 · ShellCheck 0.9

shellcheck reads a bash or sh script without running it and finds bugs: unquoted variables, unchecked cd.

Cheat sheet

8 commands Printable version
  • 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

Overview

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:

text
student@ubuntu:~$ shellcheck /tmp/posix.sh

In /tmp/posix.sh line 2:
if [[ -f x ]]; then echo y; fi
   ^--------^ SC3010 (warning): In POSIX sh, [[ ]] is undefined.

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.

Common findings

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

Options

8

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.

Recipes

5

A check before running or in CI

bash
shellcheck -S warning scripts/*.sh && echo "no findings"

With -S warning, info and style findings are neither printed nor counted: the code is 1 only if warnings or errors remain.

Silence one finding in one place

bash
# shellcheck disable=SC2086
rsync $RSYNC_OPTS src/ dst/   # the options are meant to split

The comment covers the next command. Write the reason next to it: in a month it will not be obvious.

Silence a finding for a whole directory

A .shellcheckrc file in the script's directory or above it:

text
disable=SC2034

The script sources another file

bash
shellcheck -x deploy.sh               # follow files pulled in by source
# shellcheck source=lib/common.sh     # path when source uses a var

Without -x, shellcheck prints SC1091 … Not following and does not see the functions from the sourced file.

Look at the ready-made fixes

text
student@ubuntu:~$ shellcheck -f diff /tmp/bad.sh
--- /tmp/bad.sh
+++ /tmp/bad.sh
@@ -1,8 +1,8 @@
 #!/bin/bash
 src=$1
-cd /var/backups
-for f in $(ls $src); do
-  cp $f /tmp/
+cd /var/backups || exit
+for f in $(ls "$src"); do
+  cp "$f" /tmp/

Only simple findings come with a fix: quotes, || exit. How to read a diff is covered in diff-patch.

Troubleshooting

4

SC2148: Tips depend on target shell

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.

openBinaryFile: does not exist

There is no file at that path: a typo in the name, or you are in the wrong directory. shellcheck exits with code 2.

A finding that does not apply

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 is quiet, yet the script fails

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.

Danger zone

1

Applying "Did you mean" blindly

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/.

Terms

1
linter
A program that checks source code without running it and points out likely bugs and risky constructs; for shell scripts, shellcheck.

See also

  • bash-scripting — the script itself: variables, conditions, loops.
  • exit-codes — the codes shellcheck teaches you to check.
  • test-conditions — [ ] and [[ ]], where quoting matters most.
  • command-syntax — quoting and how a line splits into arguments.
  • globbing — patterns instead of looping over ls output.
  • diff-patch — how to read the -f diff output.
  • bash — the shell these scripts are written for.
Loading…